mirror of
https://git.proxmox.com/git/libgit2
synced 2025-12-29 18:56:27 +00:00
Merge branch 'development' into blame
This commit is contained in:
commit
41dd999d12
@ -31,7 +31,7 @@ CC:=$(PREFIX)$(CC)
|
||||
INCLUDES= -I. -Isrc -Iinclude -Ideps/http-parser -Ideps/zlib
|
||||
|
||||
DEFINES= $(INCLUDES) -DNO_VIZ -DSTDC -DNO_GZIP -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE $(EXTRA_DEFINES)
|
||||
CFLAGS= -g $(DEFINES) -Wall -Wextra -O2 $(EXTRA_CFLAGS)
|
||||
CFLAGS= -g $(DEFINES) -Wall -Wextra -Wno-missing-field-initializers -O2 $(EXTRA_CFLAGS)
|
||||
|
||||
SRCS = $(wildcard src/*.c) $(wildcard src/transports/*.c) $(wildcard src/xdiff/*.c) $(wildcard deps/http-parser/*.c) $(wildcard deps/zlib/*.c) src/hash/hash_generic.c
|
||||
|
||||
|
||||
@ -71,19 +71,19 @@ Key
|
||||
Diff with 2 non-workdir iterators
|
||||
---------------------------------
|
||||
|
||||
Old New
|
||||
--- ---
|
||||
0 x x - nothing
|
||||
1 x B1 - added blob
|
||||
2 x T1 - added tree
|
||||
3 B1 x - removed blob
|
||||
4 B1 B1 - unmodified blob
|
||||
5 B1 B2 - modified blob
|
||||
6 B1 T1 - typechange blob -> tree
|
||||
7 T1 x - removed tree
|
||||
8 T1 B1 - typechange tree -> blob
|
||||
9 T1 T1 - unmodified tree
|
||||
10 T1 T2 - modified tree (implies modified/added/removed blob inside)
|
||||
| | Old | New | |
|
||||
|----|-----|-----|------------------------------------------------------------|
|
||||
| 0 | x | x | nothing |
|
||||
| 1 | x | B1 | added blob |
|
||||
| 2 | x | T1 | added tree |
|
||||
| 3 | B1 | x | removed blob |
|
||||
| 4 | B1 | B1 | unmodified blob |
|
||||
| 5 | B1 | B2 | modified blob |
|
||||
| 6 | B1 | T1 | typechange blob -> tree |
|
||||
| 7 | T1 | x | removed tree |
|
||||
| 8 | T1 | B1 | typechange tree -> blob |
|
||||
| 9 | T1 | T1 | unmodified tree |
|
||||
| 10 | T1 | T2 | modified tree (implies modified/added/removed blob inside) |
|
||||
|
||||
|
||||
Now, let's make the "New" iterator into a working directory iterator, so
|
||||
@ -92,23 +92,23 @@ we replace "added" items with either untracked or ignored, like this:
|
||||
Diff with non-work & workdir iterators
|
||||
--------------------------------------
|
||||
|
||||
Old New-WD
|
||||
--- ------
|
||||
0 x x - nothing
|
||||
1 x B1 - untracked blob
|
||||
2 x Bi - ignored file
|
||||
3 x T1 - untracked tree
|
||||
4 x Ti - ignored tree
|
||||
5 B1 x - removed blob
|
||||
6 B1 B1 - unmodified blob
|
||||
7 B1 B2 - modified blob
|
||||
8 B1 T1 - typechange blob -> tree
|
||||
9 B1 Ti - removed blob AND ignored tree as separate items
|
||||
10 T1 x - removed tree
|
||||
11 T1 B1 - typechange tree -> blob
|
||||
12 T1 Bi - removed tree AND ignored blob as separate items
|
||||
13 T1 T1 - unmodified tree
|
||||
14 T1 T2 - modified tree (implies modified/added/removed blob inside)
|
||||
| | Old | New | |
|
||||
|----|-----|-----|------------------------------------------------------------|
|
||||
| 0 | x | x | nothing |
|
||||
| 1 | x | B1 | untracked blob |
|
||||
| 2 | x | Bi | ignored file |
|
||||
| 3 | x | T1 | untracked tree |
|
||||
| 4 | x | Ti | ignored tree |
|
||||
| 5 | B1 | x | removed blob |
|
||||
| 6 | B1 | B1 | unmodified blob |
|
||||
| 7 | B1 | B2 | modified blob |
|
||||
| 8 | B1 | T1 | typechange blob -> tree |
|
||||
| 9 | B1 | Ti | removed blob AND ignored tree as separate items |
|
||||
| 10 | T1 | x | removed tree |
|
||||
| 11 | T1 | B1 | typechange tree -> blob |
|
||||
| 12 | T1 | Bi | removed tree AND ignored blob as separate items |
|
||||
| 13 | T1 | T1 | unmodified tree |
|
||||
| 14 | T1 | T2 | modified tree (implies modified/added/removed blob inside) |
|
||||
|
||||
Note: if there is a corresponding entry in the old tree, then a working
|
||||
directory item won't be ignored (i.e. no Bi or Ti for tracked items).
|
||||
@ -122,46 +122,47 @@ Checkout From 3 Iterators (2 not workdir, 1 workdir)
|
||||
|
||||
(base == old HEAD; target == what to checkout; actual == working dir)
|
||||
|
||||
base target actual/workdir
|
||||
---- ------ ------
|
||||
0 x x x - nothing
|
||||
1 x x B1/Bi/T1/Ti - untracked/ignored blob/tree (SAFE)
|
||||
2+ x B1 x - add blob (SAFE)
|
||||
3 x B1 B1 - independently added blob (FORCEABLE-2)
|
||||
4* x B1 B2/Bi/T1/Ti - add blob with content conflict (FORCEABLE-2)
|
||||
5+ x T1 x - add tree (SAFE)
|
||||
6* x T1 B1/Bi - add tree with blob conflict (FORCEABLE-2)
|
||||
7 x T1 T1/i - independently added tree (SAFE+MISSING)
|
||||
8 B1 x x - independently deleted blob (SAFE+MISSING)
|
||||
9- B1 x B1 - delete blob (SAFE)
|
||||
10- B1 x B2 - delete of modified blob (FORCEABLE-1)
|
||||
11 B1 x T1/Ti - independently deleted blob AND untrack/ign tree (SAFE+MISSING !!!)
|
||||
12 B1 B1 x - locally deleted blob (DIRTY || SAFE+CREATE)
|
||||
13+ B1 B2 x - update to deleted blob (SAFE+MISSING)
|
||||
14 B1 B1 B1 - unmodified file (SAFE)
|
||||
15 B1 B1 B2 - locally modified file (DIRTY)
|
||||
16+ B1 B2 B1 - update unmodified blob (SAFE)
|
||||
17 B1 B2 B2 - independently updated blob (FORCEABLE-1)
|
||||
18+ B1 B2 B3 - update to modified blob (FORCEABLE-1)
|
||||
19 B1 B1 T1/Ti - locally deleted blob AND untrack/ign tree (DIRTY)
|
||||
20* B1 B2 T1/Ti - update to deleted blob AND untrack/ign tree (F-1)
|
||||
21+ B1 T1 x - add tree with locally deleted blob (SAFE+MISSING)
|
||||
22* B1 T1 B1 - add tree AND deleted blob (SAFE)
|
||||
23* B1 T1 B2 - add tree with delete of modified blob (F-1)
|
||||
24 B1 T1 T1 - add tree with deleted blob (F-1)
|
||||
25 T1 x x - independently deleted tree (SAFE+MISSING)
|
||||
26 T1 x B1/Bi - independently deleted tree AND untrack/ign blob (F-1)
|
||||
27- T1 x T1 - deleted tree (MAYBE SAFE)
|
||||
28+ T1 B1 x - deleted tree AND added blob (SAFE+MISSING)
|
||||
29 T1 B1 B1 - independently typechanged tree -> blob (F-1)
|
||||
30+ T1 B1 B2 - typechange tree->blob with conflicting blob (F-1)
|
||||
31* T1 B1 T1/T2 - typechange tree->blob (MAYBE SAFE)
|
||||
32+ T1 T1 x - restore locally deleted tree (SAFE+MISSING)
|
||||
33 T1 T1 B1/Bi - locally typechange tree->untrack/ign blob (DIRTY)
|
||||
34 T1 T1 T1/T2 - unmodified tree (MAYBE SAFE)
|
||||
35+ T1 T2 x - update locally deleted tree (SAFE+MISSING)
|
||||
36* T1 T2 B1/Bi - update to tree with typechanged tree->blob conflict (F-1)
|
||||
37 T1 T2 T1/T2/T3 - update to existing tree (MAYBE SAFE)
|
||||
| |base | target | actual/workdir | |
|
||||
|-----|-----|------- |----------------|--------------------------------------------------------------------|
|
||||
| 0 | x | x | x | nothing |
|
||||
| 1 | x | x | B1/Bi/T1/Ti | untracked/ignored blob/tree (SAFE) |
|
||||
| 2+ | x | B1 | x | add blob (SAFE) |
|
||||
| 3 | x | B1 | B1 | independently added blob (FORCEABLE-2) |
|
||||
| 4* | x | B1 | B2/Bi/T1/Ti | add blob with content conflict (FORCEABLE-2) |
|
||||
| 5+ | x | T1 | x | add tree (SAFE) |
|
||||
| 6* | x | T1 | B1/Bi | add tree with blob conflict (FORCEABLE-2) |
|
||||
| 7 | x | T1 | T1/i | independently added tree (SAFE+MISSING) |
|
||||
| 8 | B1 | x | x | independently deleted blob (SAFE+MISSING) |
|
||||
| 9- | B1 | x | B1 | delete blob (SAFE) |
|
||||
| 10- | B1 | x | B2 | delete of modified blob (FORCEABLE-1) |
|
||||
| 11 | B1 | x | T1/Ti | independently deleted blob AND untrack/ign tree (SAFE+MISSING !!!) |
|
||||
| 12 | B1 | B1 | x | locally deleted blob (DIRTY || SAFE+CREATE) |
|
||||
| 13+ | B1 | B2 | x | update to deleted blob (SAFE+MISSING) |
|
||||
| 14 | B1 | B1 | B1 | unmodified file (SAFE) |
|
||||
| 15 | B1 | B1 | B2 | locally modified file (DIRTY) |
|
||||
| 16+ | B1 | B2 | B1 | update unmodified blob (SAFE) |
|
||||
| 17 | B1 | B2 | B2 | independently updated blob (FORCEABLE-1) |
|
||||
| 18+ | B1 | B2 | B3 | update to modified blob (FORCEABLE-1) |
|
||||
| 19 | B1 | B1 | T1/Ti | locally deleted blob AND untrack/ign tree (DIRTY) |
|
||||
| 20* | B1 | B2 | T1/Ti | update to deleted blob AND untrack/ign tree (F-1) |
|
||||
| 21+ | B1 | T1 | x | add tree with locally deleted blob (SAFE+MISSING) |
|
||||
| 22* | B1 | T1 | B1 | add tree AND deleted blob (SAFE) |
|
||||
| 23* | B1 | T1 | B2 | add tree with delete of modified blob (F-1) |
|
||||
| 24 | B1 | T1 | T1 | add tree with deleted blob (F-1) |
|
||||
| 25 | T1 | x | x | independently deleted tree (SAFE+MISSING) |
|
||||
| 26 | T1 | x | B1/Bi | independently deleted tree AND untrack/ign blob (F-1) |
|
||||
| 27- | T1 | x | T1 | deleted tree (MAYBE SAFE) |
|
||||
| 28+ | T1 | B1 | x | deleted tree AND added blob (SAFE+MISSING) |
|
||||
| 29 | T1 | B1 | B1 | independently typechanged tree -> blob (F-1) |
|
||||
| 30+ | T1 | B1 | B2 | typechange tree->blob with conflicting blob (F-1) |
|
||||
| 31* | T1 | B1 | T1/T2 | typechange tree->blob (MAYBE SAFE) |
|
||||
| 32+ | T1 | T1 | x | restore locally deleted tree (SAFE+MISSING) |
|
||||
| 33 | T1 | T1 | B1/Bi | locally typechange tree->untrack/ign blob (DIRTY) |
|
||||
| 34 | T1 | T1 | T1/T2 | unmodified tree (MAYBE SAFE) |
|
||||
| 35+ | T1 | T2 | x | update locally deleted tree (SAFE+MISSING) |
|
||||
| 36* | T1 | T2 | B1/Bi | update to tree with typechanged tree->blob conflict (F-1) |
|
||||
| 37 | T1 | T2 | T1/T2/T3 | update to existing tree (MAYBE SAFE) |
|
||||
|
||||
|
||||
The number is followed by ' ' if no change is needed or '+' if the case
|
||||
needs to write to disk or '-' if something must be deleted and '*' if
|
||||
@ -169,34 +170,34 @@ there should be a delete followed by an write.
|
||||
|
||||
There are four tiers of safe cases:
|
||||
|
||||
- SAFE == completely safe to update
|
||||
- SAFE+MISSING == safe except the workdir is missing the expect content
|
||||
- MAYBE SAFE == safe if workdir tree matches (or is missing) baseline
|
||||
* SAFE == completely safe to update
|
||||
* SAFE+MISSING == safe except the workdir is missing the expect content
|
||||
* MAYBE SAFE == safe if workdir tree matches (or is missing) baseline
|
||||
content, which is unknown at this point
|
||||
- FORCEABLE == conflict unless FORCE is given
|
||||
- DIRTY == no conflict but change is not applied unless FORCE
|
||||
* FORCEABLE == conflict unless FORCE is given
|
||||
* DIRTY == no conflict but change is not applied unless FORCE
|
||||
|
||||
Some slightly unusual circumstances:
|
||||
|
||||
8 - parent dir is only deleted when file is, so parent will be left if
|
||||
empty even though it would be deleted if the file were present
|
||||
11 - core git does not consider this a conflict but attempts to delete T1
|
||||
and gives "unable to unlink file" error yet does not skip the rest
|
||||
of the operation
|
||||
12 - without FORCE file is left deleted (i.e. not restored) so new wd is
|
||||
dirty (and warning message "D file" is printed), with FORCE, file is
|
||||
restored.
|
||||
24 - This should be considered MAYBE SAFE since effectively it is 7 and 8
|
||||
combined, but core git considers this a conflict unless forced.
|
||||
26 - This combines two cases (1 & 25) (and also implied 8 for tree content)
|
||||
which are ok on their own, but core git treat this as a conflict.
|
||||
If not forced, this is a conflict. If forced, this actually doesn't
|
||||
have to write anything and leaves the new blob as an untracked file.
|
||||
32 - This is the only case where the baseline and target values match
|
||||
and yet we will still write to the working directory. In all other
|
||||
cases, if baseline == target, we don't touch the workdir (it is
|
||||
either already right or is "dirty"). However, since this case also
|
||||
implies that a ?/B1/x case will exist as well, it can be skipped.
|
||||
* 8 - parent dir is only deleted when file is, so parent will be left if
|
||||
empty even though it would be deleted if the file were present
|
||||
* 11 - core git does not consider this a conflict but attempts to delete T1
|
||||
and gives "unable to unlink file" error yet does not skip the rest
|
||||
of the operation
|
||||
* 12 - without FORCE file is left deleted (i.e. not restored) so new wd is
|
||||
dirty (and warning message "D file" is printed), with FORCE, file is
|
||||
restored.
|
||||
* 24 - This should be considered MAYBE SAFE since effectively it is 7 and 8
|
||||
combined, but core git considers this a conflict unless forced.
|
||||
* 26 - This combines two cases (1 & 25) (and also implied 8 for tree content)
|
||||
which are ok on their own, but core git treat this as a conflict.
|
||||
If not forced, this is a conflict. If forced, this actually doesn't
|
||||
have to write anything and leaves the new blob as an untracked file.
|
||||
* 32 - This is the only case where the baseline and target values match
|
||||
and yet we will still write to the working directory. In all other
|
||||
cases, if baseline == target, we don't touch the workdir (it is
|
||||
either already right or is "dirty"). However, since this case also
|
||||
implies that a ?/B1/x case will exist as well, it can be skipped.
|
||||
|
||||
Cases 3, 17, 24, 26, and 29 are all considered conflicts even though
|
||||
none of them will require making any updates to the working directory.
|
||||
|
||||
@ -45,44 +45,48 @@ Internal Objects
|
||||
* `git_diff_file_content` is an internal structure that represents the
|
||||
data on one side of an item to be diffed; it is an augmented
|
||||
`git_diff_file` with more flags and the actual file data.
|
||||
** it is created from a repository plus a) a git_diff_file, b) a git_blob,
|
||||
|
||||
* it is created from a repository plus a) a git_diff_file, b) a git_blob,
|
||||
or c) raw data and size
|
||||
** there are three main operations on git_diff_file_content:
|
||||
*** _initialization_ sets up the data structure and does what it can up to,
|
||||
but not including loading and looking at the actual data
|
||||
*** _loading_ loads the data, preprocesses it (i.e. applies filters) and
|
||||
potentially analyzes it (to decide if binary)
|
||||
*** _free_ releases loaded data and frees any allocated memory
|
||||
* there are three main operations on git_diff_file_content:
|
||||
|
||||
* _initialization_ sets up the data structure and does what it can up to,
|
||||
but not including loading and looking at the actual data
|
||||
* _loading_ loads the data, preprocesses it (i.e. applies filters) and
|
||||
potentially analyzes it (to decide if binary)
|
||||
* _free_ releases loaded data and frees any allocated memory
|
||||
|
||||
* The internal structure of a `git_diff_patch` stores the actual diff
|
||||
between a pair of `git_diff_file_content` items
|
||||
** it may be "unset" if the items are not diffable
|
||||
** "empty" if the items are the same
|
||||
** otherwise it will consist of a set of hunks each of which covers some
|
||||
number of lines of context, additions and deletions
|
||||
** a patch is created from two git_diff_file_content items
|
||||
** a patch is fully instantiated in three phases:
|
||||
*** initial creation and initialization
|
||||
*** loading of data and preliminary data examination
|
||||
*** diffing of data and optional storage of diffs
|
||||
** (TBD) if a patch is asked to store the diffs and the size of the diff
|
||||
is significantly smaller than the raw data of the two sides, then the
|
||||
patch may be flattened using a pool of string data
|
||||
|
||||
* it may be "unset" if the items are not diffable
|
||||
* "empty" if the items are the same
|
||||
* otherwise it will consist of a set of hunks each of which covers some
|
||||
number of lines of context, additions and deletions
|
||||
* a patch is created from two git_diff_file_content items
|
||||
* a patch is fully instantiated in three phases:
|
||||
|
||||
* initial creation and initialization
|
||||
* loading of data and preliminary data examination
|
||||
* diffing of data and optional storage of diffs
|
||||
* (TBD) if a patch is asked to store the diffs and the size of the diff
|
||||
is significantly smaller than the raw data of the two sides, then the
|
||||
patch may be flattened using a pool of string data
|
||||
|
||||
* `git_diff_output` is an internal structure that represents an output
|
||||
target for a `git_diff_patch`
|
||||
** It consists of file, hunk, and line callbacks, plus a payload
|
||||
** There is a standard flattened output that can be used for plain text output
|
||||
** Typically we use a `git_xdiff_output` which drives the callbacks via the
|
||||
xdiff code taken from core Git.
|
||||
* It consists of file, hunk, and line callbacks, plus a payload
|
||||
* There is a standard flattened output that can be used for plain text output
|
||||
* Typically we use a `git_xdiff_output` which drives the callbacks via the
|
||||
xdiff code taken from core Git.
|
||||
|
||||
* `git_diff_driver` is an internal structure that encapsulates the logic
|
||||
for a given type of file
|
||||
** a driver is looked up based on the name and mode of a file.
|
||||
** the driver can then be used to:
|
||||
*** determine if a file is binary (by attributes, by git_diff_options
|
||||
settings, or by examining the content)
|
||||
*** give you a function pointer that is used to evaluate function context
|
||||
for hunk headers
|
||||
** At some point, the logic for getting a filtered version of file content
|
||||
or calculating the OID of a file may be moved into the driver.
|
||||
* a driver is looked up based on the name and mode of a file.
|
||||
* the driver can then be used to:
|
||||
* determine if a file is binary (by attributes, by git_diff_options
|
||||
settings, or by examining the content)
|
||||
* give you a function pointer that is used to evaluate function context
|
||||
for hunk headers
|
||||
* At some point, the logic for getting a filtered version of file content
|
||||
or calculating the OID of a file may be moved into the driver.
|
||||
|
||||
@ -1,111 +1,270 @@
|
||||
Error reporting in libgit2
|
||||
==========================
|
||||
|
||||
Error reporting is performed on an explicit `git_error **` argument, which appears at the end of all API calls that can return an error. Yes, this does clutter the API.
|
||||
Libgit2 tries to follow the POSIX style: functions return an `int` value
|
||||
with 0 (zero) indicating success and negative values indicating an error.
|
||||
There are specific negative error codes for each "expected failure"
|
||||
(e.g. `GIT_ENOTFOUND` for files that take a path which might be missing)
|
||||
and a generic error code (-1) for all critical or non-specific failures
|
||||
(e.g. running out of memory or system corruption).
|
||||
|
||||
When a function fails, an error is set on the error variable **and** returns one of the generic error codes.
|
||||
When a negative value is returned, an error message is also set. The
|
||||
message can be accessed via the `giterr_last` function which will return a
|
||||
pointer to a `git_error` structure containing the error message text and
|
||||
the class of error (i.e. what part of the library generated the error).
|
||||
|
||||
For instance: An object lookup by SHA prefix (`git_object_lookup_prefix`)
|
||||
has two expected failure cases: the SHA is not found at all which returns
|
||||
`GIT_ENOTFOUND` or the SHA prefix is ambiguous (i.e. two or more objects
|
||||
share the prefix) which returns `GIT_EAMBIGUOUS`. There are any number of
|
||||
critical failures (such as a packfile being corrupted, a loose object
|
||||
having the wrong access permissions, etc.) all of which will return -1.
|
||||
When the object lookup is successful, it will return 0.
|
||||
|
||||
If libgit2 was compiled with threads enabled (`-DTHREADSAFE=ON` when using
|
||||
CMake), then the error message will be kept in thread-local storage, so it
|
||||
will not be modified by other threads. If threads are not enabled, then
|
||||
the error message is in global data.
|
||||
|
||||
All of the error return codes, the `git_error` type, the error access
|
||||
functions, and the error classes are defined in `include/git2/errors.h`.
|
||||
See the documentation there for details on the APIs for accessing,
|
||||
clearing, and even setting error codes.
|
||||
|
||||
When writing libgit2 code, please be smart and conservative when returning
|
||||
error codes. Functions usually have a maximum of two or three "expected
|
||||
errors" and in most cases only one. If you feel there are more possible
|
||||
expected error scenarios, then the API you are writing may be at too high
|
||||
a level for core libgit2.
|
||||
|
||||
Example usage
|
||||
-------------
|
||||
|
||||
When using libgit2, you will typically capture the return value from
|
||||
functions using an `int` variable and check to see if it is negative.
|
||||
When that happens, you can, if you wish, look at the specific value or
|
||||
look at the error message that was generated.
|
||||
|
||||
~~~c
|
||||
int git_repository_open(git_repository **repository, const char *path, git_error **error)
|
||||
{
|
||||
// perform some opening
|
||||
if (p_exists(path) < 0) {
|
||||
giterr_set(error, GITERR_REPOSITORY, "The path '%s' doesn't exist", path);
|
||||
return GIT_ENOTFOUND;
|
||||
git_repository *repo;
|
||||
int error = git_repository_open(&repo, "path/to/repo");
|
||||
|
||||
if (error < 0) {
|
||||
fprintf(stderr, "Could not open repository: %s\n", giterr_last()->message);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
...
|
||||
... use `repo` here ...
|
||||
|
||||
if (try_to_parse(path, error) < 0)
|
||||
return GIT_ERROR;
|
||||
git_repository_free(repo); /* void function - no error return code */
|
||||
}
|
||||
~~~
|
||||
|
||||
Some of the error return values do have meaning. Optionally, you can look
|
||||
at the specific error values to decide what to do.
|
||||
|
||||
~~~c
|
||||
{
|
||||
git_repository *repo;
|
||||
const char *path = "path/to/repo";
|
||||
int error = git_repository_open(&repo, path);
|
||||
|
||||
if (error < 0) {
|
||||
if (error == GIT_ENOTFOUND)
|
||||
fprintf(stderr, "Could not find repository at path '%s'\n", path);
|
||||
else
|
||||
fprintf(stderr, "Unable to open repository: %s\n",
|
||||
giterr_last()->message);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
... happy ...
|
||||
}
|
||||
~~~
|
||||
|
||||
Some of the higher-level language bindings may use a range of information
|
||||
from libgit2 to convert error return codes into exceptions, including the
|
||||
specific error return codes and even the class of error and the error
|
||||
message returned by `giterr_last`, but the full range of that logic is
|
||||
beyond the scope of this document.
|
||||
|
||||
Example internal implementation
|
||||
-------------------------------
|
||||
|
||||
Internally, libgit2 detects error scenarios, records error messages, and
|
||||
returns error values. Errors from low-level functions are generally
|
||||
passed upwards (unless the higher level can either handle the error or
|
||||
wants to translate the error into something more meaningful).
|
||||
|
||||
~~~c
|
||||
int git_repository_open(git_repository **repository, const char *path)
|
||||
{
|
||||
/* perform some logic to open the repository */
|
||||
if (p_exists(path) < 0) {
|
||||
giterr_set(GITERR_REPOSITORY, "The path '%s' doesn't exist", path);
|
||||
return GIT_ENOTFOUND;
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
~~~
|
||||
|
||||
The simple error API
|
||||
The public error API
|
||||
--------------------
|
||||
|
||||
- `void giterr_set(git_error **, int, const char *, ...)`: the main function used to set an error. It allocates a new error object and stores it in the passed error pointer. It has no return value. The arguments for `giterr_set` are as follows:
|
||||
- `const git_error *giterr_last(void)`: The main function used to look up
|
||||
the last error. This may return NULL if no error has occurred.
|
||||
Otherwise this should return a `git_error` object indicating the class
|
||||
of error and the error message that was generated by the library.
|
||||
|
||||
- `git_error **error_ptr`: the pointer where the error will be created.
|
||||
- `int error_class`: the class for the error. This is **not** an error code: this is an specific enum that specifies the error family. The point is to map these families 1-1 with Exception types on higher level languages (e.g. GitRepositoryException)
|
||||
- `const char *error_str, ...`: the error string, with optional formatting arguments
|
||||
The last error is stored in thread-local storage when libgit2 is
|
||||
compiled with thread support, so you do not have to worry about another
|
||||
thread overwriting the value. When thread support is off, the last
|
||||
error is a global value.
|
||||
|
||||
- `void giterr_free(git_error *)`: takes an error and frees it. This function is available in the external API.
|
||||
_Note_ There are some known bugs in the library where this may return
|
||||
NULL even when an error code was generated. Please report these as
|
||||
bugs, but in the meantime, please code defensively and check for NULL
|
||||
when calling this function.
|
||||
|
||||
- `void giterr_clear(git_error **)`: clears an error previously set in an error pointer, setting it to NULL and calling `giterr_free` on it.
|
||||
- `void geterr_clear(void)`: This function clears the last error. The
|
||||
library will call this when an error is generated by low level function
|
||||
and the higher level function handles the error.
|
||||
|
||||
- `void giterr_propagate(git_error **, git_error *)`: moves an error to a given error pointer, handling the case when the error pointer is NULL (in that case the error gets freed, because it cannot be propagated).
|
||||
_Note_ There are some known bugs in the library where a low level
|
||||
function's error message is not cleared by higher level code that
|
||||
handles the error and returns zero. Please report these as bugs, but in
|
||||
the meantime, a zero return value from a libgit2 API does not guarantee
|
||||
that `giterr_last()` will return NULL.
|
||||
|
||||
The new error code return values
|
||||
--------------------------------
|
||||
- `void giterr_set_str(int error_class, const char *message)`: This
|
||||
function can be used when writing a custom backend module to set the
|
||||
libgit2 error message. See the documentation on this function for its
|
||||
use. Normal usage of libgit2 will probably never need to call this API.
|
||||
|
||||
We are doing this the POSIX way: one error code for each "expected failure", and a generic error code for all the critical failures.
|
||||
- `void giterr_set_oom(void)`: This is a standard function for reporting
|
||||
an out-of-memory error. It is written in a manner that it doesn't have
|
||||
to allocate any extra memory in order to record the error, so this is
|
||||
the best way to report that scenario.
|
||||
|
||||
For instance: A reference lookup can have an expected failure (which is when the reference cannot be found), and a critical failure (which could be any of a long list of things that could go wrong, such as the refs packfile being corrupted, a loose ref being written with the wrong permissions, etc). We cannot have distinct error codes for every single error in the library, hence `git_reference_lookup` would return GIT_SUCCESS if the operation was successful, GIT_ENOTFOUND when the reference doesn't exist, and GIT_ERROR when an error happens -- **the error is then detailed in the `git_error` parameter**.
|
||||
Deviations from the standard
|
||||
----------------------------
|
||||
|
||||
Please be smart when returning error codes. Functions have max two "expected errors", and in most cases only one.
|
||||
There are some public functions that do not return `int` values. There
|
||||
are two primary cases:
|
||||
|
||||
* `void` return values: If a function has a `void` return, then it will
|
||||
never fail. This primary will be used for object destructors.
|
||||
|
||||
* `git_xyz *` return values: These are simple accessor functions where the
|
||||
only meaningful error would typically be looking something up by index
|
||||
and having the index be out of bounds. In those cases, the function
|
||||
will typically return NULL.
|
||||
|
||||
* Boolean return values: There are some cases where a function cannot fail
|
||||
and wants to return a boolean value. In those cases, we try to return 1
|
||||
for true and 0 for false. These cases are rare and the return value for
|
||||
the function should probably be an `unsigned int` to denote these cases.
|
||||
If you find an exception, please open an issue and let's fix it.
|
||||
|
||||
There are a few other exceptions to these rules here and there in the
|
||||
library, but those are extremely rare and should probably be converted
|
||||
over to other to more standard patterns for usage. Feel free to open
|
||||
issues pointing these out.
|
||||
|
||||
There are some known bugs in the library where some functions may return a
|
||||
negative value but not set an error message and some other functions may
|
||||
return zero (no error) and yet leave an error message set. Please report
|
||||
these cases as issues and they will be fixed. In the meanwhile, please
|
||||
code defensively, checking that the return value of `giterr_last` is not
|
||||
NULL before using it, and not relying on `giterr_last` to return NULL when
|
||||
a function returns 0 for success.
|
||||
|
||||
The internal error API
|
||||
----------------------
|
||||
|
||||
- `void giterr_set(int error_class, const char *fmt, ...)`: This is the
|
||||
main internal function for setting an error. It works like `printf` to
|
||||
format the error message. See the notes of `giterr_set_str` for a
|
||||
general description of how error messages are stored (and also about
|
||||
special handling for `error_class` of `GITERR_OS`).
|
||||
|
||||
Writing error messages
|
||||
----------------------
|
||||
|
||||
Here are some guidelines when writing error messages:
|
||||
|
||||
- Use proper English, and an impersonal or past tenses: *The given path does not exist*, *Failed to lookup object in ODB*
|
||||
- Use proper English, and an impersonal or past tenses: *The given path
|
||||
does not exist*, *Failed to lookup object in ODB*
|
||||
|
||||
- Use short, direct and objective messages. **One line, max**. libgit2 is a low level library: think that all the messages reported will be thrown as Ruby or Python exceptions. Think how long are common exception messages in those languages.
|
||||
- Use short, direct and objective messages. **One line, max**. libgit2 is
|
||||
a low level library: think that all the messages reported will be thrown
|
||||
as Ruby or Python exceptions. Think how long are common exception
|
||||
messages in those languages.
|
||||
|
||||
- **Do not add redundant information to the error message**, specially information that can be inferred from the context.
|
||||
- **Do not add redundant information to the error message**, specially
|
||||
information that can be inferred from the context.
|
||||
|
||||
E.g. in `git_repository_open`, do not report a message like "Failed to open repository: path not found". Somebody is
|
||||
calling that function. If it fails, he already knows that the repository failed to open!
|
||||
E.g. in `git_repository_open`, do not report a message like "Failed to
|
||||
open repository: path not found". Somebody is calling that
|
||||
function. If it fails, they already know that the repository failed to
|
||||
open!
|
||||
|
||||
General guidelines for error reporting
|
||||
--------------------------------------
|
||||
|
||||
- We never handle programming errors with these functions. Programming errors are `assert`ed, and when their source is internal, fixed as soon as possible. This is C, people.
|
||||
- Libgit2 does not handle programming errors with these
|
||||
functions. Programming errors are `assert`ed, and when their source is
|
||||
internal, fixed as soon as possible. This is C, people.
|
||||
|
||||
Example of programming errors that would **not** be handled: passing NULL to a function that expects a valid pointer; passing a `git_tree` to a function that expects a `git_commit`. All these cases need to be identified with `assert` and fixed asap.
|
||||
Example of programming errors that would **not** be handled: passing
|
||||
NULL to a function that expects a valid pointer; passing a `git_tree`
|
||||
to a function that expects a `git_commit`. All these cases need to be
|
||||
identified with `assert` and fixed asap.
|
||||
|
||||
Example of a runtime error: failing to parse a `git_tree` because it contains invalid data. Failing to open a file because it doesn't exist on disk. These errors would be handled, and a `git_error` would be set.
|
||||
Example of a runtime error: failing to parse a `git_tree` because it
|
||||
contains invalid data. Failing to open a file because it doesn't exist
|
||||
on disk. These errors are handled, a meaningful error message is set,
|
||||
and an error code is returned.
|
||||
|
||||
- The `git_error **` argument is always the last in the signature of all API calls. No exceptions.
|
||||
- In general, *do not* try to overwrite errors internally and *do*
|
||||
propagate error codes from lower level functions to the higher level.
|
||||
There are some cases where propagating an error code will be more
|
||||
confusing rather than less, so there are some exceptions to this rule,
|
||||
but the default behavior should be to simply clean up and pass the error
|
||||
on up to the caller.
|
||||
|
||||
- When the programmer (or us, internally) doesn't need error handling, he can pass `NULL` to the `git_error **` param. This means that the errors won't be *reported*, but obviously they still will be handled (i.e. the failing function will interrupt and return cleanly). This is transparently handled by `giterr_set`
|
||||
|
||||
- `git_error *` **must be initialized to `NULL` before passing its value to a function!!**
|
||||
**WRONG**
|
||||
|
||||
~~~c
|
||||
git_error *err;
|
||||
git_error *good_error = NULL;
|
||||
|
||||
git_foo_func(arg1, arg2, &error); // invalid: `error` is not initialized
|
||||
git_foo_func2(arg1, arg2, &good_error); // OK!
|
||||
git_foo_func3(arg1, arg2, NULL); // OK! But no error reporting!
|
||||
~~~
|
||||
|
||||
- Piling up errors is an error! Don't do this! Errors must always be free'd when a function returns.
|
||||
|
||||
~~~c
|
||||
git_error *error = NULL;
|
||||
|
||||
git_foo_func1(arg1, &error);
|
||||
git_foo_func2(arg2, &error); // WRONG! What if func1 failed? `error` would leak!
|
||||
~~~
|
||||
|
||||
- Likewise: do not rethrow errors internally!
|
||||
|
||||
~~~c
|
||||
int git_commit_create(..., git_error **error)
|
||||
int git_commit_parent(...)
|
||||
{
|
||||
if (git_reference_exists("HEAD", error) < 0) {
|
||||
/* HEAD does not exist; create it so we can commit... */
|
||||
if (git_reference_create("HEAD", error) < 0) {
|
||||
/* error could be rethrown */
|
||||
}
|
||||
...
|
||||
|
||||
if (git_commit_lookup(parent, repo, parent_id) < 0) {
|
||||
giterr_set(GITERR_COMMIT, "Overwrite lookup error message");
|
||||
return -1; /* mask error code */
|
||||
}
|
||||
|
||||
- Remember that errors are now allocated, and hence they need to be free'd after they've been used. Failure to do so internally (e.g. in the already seen examples of error piling) will be reported by Valgrind, so we can easily find where are we rethrowing errors.
|
||||
...
|
||||
}
|
||||
~~~
|
||||
|
||||
- Remember that any function that fails **will set an error object**, and that object will be freed.
|
||||
**RIGHT**
|
||||
|
||||
~~~c
|
||||
int git_commit_parent(...)
|
||||
{
|
||||
...
|
||||
|
||||
error = git_commit_lookup(parent, repo, parent_id);
|
||||
if (error < 0) {
|
||||
/* cleanup intermediate objects if necessary */
|
||||
/* leave error message and propagate error code */
|
||||
return error;
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
~~~
|
||||
|
||||
@ -12,6 +12,8 @@ int main (int argc, char** argv)
|
||||
char out[41];
|
||||
out[40] = '\0';
|
||||
|
||||
git_threads_init();
|
||||
|
||||
if (argc > 1)
|
||||
dir = argv[1];
|
||||
if (!dir || argc > 2) {
|
||||
@ -62,6 +64,8 @@ int main (int argc, char** argv)
|
||||
git_index_free(index);
|
||||
git_repository_free(repo);
|
||||
|
||||
git_threads_shutdown();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -71,7 +71,7 @@ static void show_branch(git_repository *repo, int format)
|
||||
|
||||
error = git_repository_head(&head, repo);
|
||||
|
||||
if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND)
|
||||
if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND)
|
||||
branch = NULL;
|
||||
else if (!error) {
|
||||
branch = git_reference_name(head);
|
||||
|
||||
@ -59,4 +59,7 @@
|
||||
#include "git2/pathspec.h"
|
||||
#include "git2/blame.h"
|
||||
|
||||
#include "git2/buffer.h"
|
||||
#include "git2/filter.h"
|
||||
|
||||
#endif
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#include "types.h"
|
||||
#include "oid.h"
|
||||
#include "object.h"
|
||||
#include "buffer.h"
|
||||
|
||||
/**
|
||||
* @file git2/blob.h
|
||||
@ -95,6 +96,37 @@ GIT_EXTERN(const void *) git_blob_rawcontent(const git_blob *blob);
|
||||
*/
|
||||
GIT_EXTERN(git_off_t) git_blob_rawsize(const git_blob *blob);
|
||||
|
||||
/**
|
||||
* Get a buffer with the filtered content of a blob.
|
||||
*
|
||||
* This applies filters as if the blob was being checked out to the
|
||||
* working directory under the specified filename. This may apply
|
||||
* CRLF filtering or other types of changes depending on the file
|
||||
* attributes set for the blob and the content detected in it.
|
||||
*
|
||||
* The output is written into a `git_buf` which the caller must free
|
||||
* when done (via `git_buf_free`).
|
||||
*
|
||||
* If no filters need to be applied, then the `out` buffer will just be
|
||||
* populated with a pointer to the raw content of the blob. In that case,
|
||||
* be careful to *not* free the blob until done with the buffer. To keep
|
||||
* the data detached from the blob, call `git_buf_grow` on the buffer
|
||||
* with a `want_size` of 0 and the buffer will be reallocated to be
|
||||
* detached from the blob.
|
||||
*
|
||||
* @param out The git_buf to be filled in
|
||||
* @param blob Pointer to the blob
|
||||
* @param as_path Path used for file attribute lookups, etc.
|
||||
* @param check_for_binary_data Should this test if blob content contains
|
||||
* NUL bytes / looks like binary data before applying filters?
|
||||
* @return 0 on success or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_blob_filtered_content(
|
||||
git_buf *out,
|
||||
git_blob *blob,
|
||||
const char *as_path,
|
||||
int check_for_binary_data);
|
||||
|
||||
/**
|
||||
* Read a file from the working folder of a repository
|
||||
* and write it to the Object Database as a loose blob
|
||||
|
||||
112
include/git2/buffer.h
Normal file
112
include/git2/buffer.h
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef INCLUDE_git_buf_h__
|
||||
#define INCLUDE_git_buf_h__
|
||||
|
||||
#include "common.h"
|
||||
|
||||
/**
|
||||
* @file git2/buffer.h
|
||||
* @brief Buffer export structure
|
||||
*
|
||||
* @ingroup Git
|
||||
* @{
|
||||
*/
|
||||
GIT_BEGIN_DECL
|
||||
|
||||
/**
|
||||
* A data buffer for exporting data from libgit2
|
||||
*
|
||||
* Sometimes libgit2 wants to return an allocated data buffer to the
|
||||
* caller and have the caller take responsibility for freeing that memory.
|
||||
* This can be awkward if the caller does not have easy access to the same
|
||||
* allocation functions that libgit2 is using. In those cases, libgit2
|
||||
* will fill in a `git_buf` and the caller can use `git_buf_free()` to
|
||||
* release it when they are done.
|
||||
*
|
||||
* A `git_buf` may also be used for the caller to pass in a reference to
|
||||
* a block of memory they hold. In this case, libgit2 will not resize or
|
||||
* free the memory, but will read from it as needed.
|
||||
*
|
||||
* A `git_buf` is a public structure with three fields:
|
||||
*
|
||||
* - `ptr` points to the start of the allocated memory. If it is NULL,
|
||||
* then the `git_buf` is considered empty and libgit2 will feel free
|
||||
* to overwrite it with new data.
|
||||
*
|
||||
* - `size` holds the size (in bytes) of the data that is actually used.
|
||||
*
|
||||
* - `asize` holds the known total amount of allocated memory if the `ptr`
|
||||
* was allocated by libgit2. It may be larger than `size`. If `ptr`
|
||||
* was not allocated by libgit2 and should not be resized and/or freed,
|
||||
* then `asize` will be set to zero.
|
||||
*
|
||||
* Some APIs may occasionally do something slightly unusual with a buffer,
|
||||
* such as setting `ptr` to a value that was passed in by the user. In
|
||||
* those cases, the behavior will be clearly documented by the API.
|
||||
*/
|
||||
typedef struct {
|
||||
char *ptr;
|
||||
size_t asize, size;
|
||||
} git_buf;
|
||||
|
||||
/**
|
||||
* Static initializer for git_buf from static buffer
|
||||
*/
|
||||
#define GIT_BUF_INIT_CONST(STR,LEN) { (char *)(STR), 0, (size_t)(LEN) }
|
||||
|
||||
/**
|
||||
* Free the memory referred to by the git_buf.
|
||||
*
|
||||
* Note that this does not free the `git_buf` itself, just the memory
|
||||
* pointed to by `buffer->ptr`. This will not free the memory if it looks
|
||||
* like it was not allocated internally, but it will clear the buffer back
|
||||
* to the empty state.
|
||||
*
|
||||
* @param buffer The buffer to deallocate
|
||||
*/
|
||||
GIT_EXTERN(void) git_buf_free(git_buf *buffer);
|
||||
|
||||
/**
|
||||
* Resize the buffer allocation to make more space.
|
||||
*
|
||||
* This will attempt to grow the buffer to accomodate the target size.
|
||||
*
|
||||
* If the buffer refers to memory that was not allocated by libgit2 (i.e.
|
||||
* the `asize` field is zero), then `ptr` will be replaced with a newly
|
||||
* allocated block of data. Be careful so that memory allocated by the
|
||||
* caller is not lost. As a special variant, if you pass `target_size` as
|
||||
* 0 and the memory is not allocated by libgit2, this will allocate a new
|
||||
* buffer of size `size` and copy the external data into it.
|
||||
*
|
||||
* Currently, this will never shrink a buffer, only expand it.
|
||||
*
|
||||
* If the allocation fails, this will return an error and the buffer will be
|
||||
* marked as invalid for future operations, invaliding the contents.
|
||||
*
|
||||
* @param buffer The buffer to be resized; may or may not be allocated yet
|
||||
* @param target_size The desired available size
|
||||
* @return 0 on success, -1 on allocation failure
|
||||
*/
|
||||
GIT_EXTERN(int) git_buf_grow(git_buf *buffer, size_t target_size);
|
||||
|
||||
/**
|
||||
* Set buffer to a copy of some raw data.
|
||||
*
|
||||
* @param buffer The buffer to set
|
||||
* @param data The data to copy into the buffer
|
||||
* @param datalen The length of the data to copy into the buffer
|
||||
* @return 0 on success, -1 on allocation failure
|
||||
*/
|
||||
GIT_EXTERN(int) git_buf_set(
|
||||
git_buf *buffer, const void *data, size_t datalen);
|
||||
|
||||
GIT_END_DECL
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif
|
||||
@ -249,7 +249,7 @@ typedef struct git_checkout_opts {
|
||||
*
|
||||
* @param repo repository to check out (must be non-bare)
|
||||
* @param opts specifies checkout options (may be NULL)
|
||||
* @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing
|
||||
* @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing
|
||||
* branch, GIT_ERROR otherwise (use giterr_last for information
|
||||
* about the error)
|
||||
*/
|
||||
|
||||
@ -136,7 +136,9 @@ typedef enum {
|
||||
GIT_OPT_SET_CACHE_OBJECT_LIMIT,
|
||||
GIT_OPT_SET_CACHE_MAX_SIZE,
|
||||
GIT_OPT_ENABLE_CACHING,
|
||||
GIT_OPT_GET_CACHED_MEMORY
|
||||
GIT_OPT_GET_CACHED_MEMORY,
|
||||
GIT_OPT_GET_TEMPLATE_PATH,
|
||||
GIT_OPT_SET_TEMPLATE_PATH
|
||||
} git_libgit2_opt_t;
|
||||
|
||||
/**
|
||||
@ -210,6 +212,18 @@ typedef enum {
|
||||
* > Get the current bytes in cache and the maximum that would be
|
||||
* > allowed in the cache.
|
||||
*
|
||||
* * opts(GIT_OPT_GET_TEMPLATE_PATH, char *out, size_t len)
|
||||
*
|
||||
* > Get the default template path.
|
||||
* > The path is written to the `out`
|
||||
* > buffer up to size `len`. Returns GIT_EBUFS if buffer is too small.
|
||||
*
|
||||
* * opts(GIT_OPT_SET_TEMPLATE_PATH, const char *path)
|
||||
*
|
||||
* > Set the default template path.
|
||||
* >
|
||||
* > - `path` directory of template.
|
||||
*
|
||||
* @param option Option key
|
||||
* @param ... value to set the option
|
||||
* @return 0 on success, <0 on failure
|
||||
|
||||
@ -27,7 +27,7 @@ typedef enum {
|
||||
GIT_EBUFS = -6,
|
||||
GIT_EUSER = -7,
|
||||
GIT_EBAREREPO = -8,
|
||||
GIT_EORPHANEDHEAD = -9,
|
||||
GIT_EUNBORNBRANCH = -9,
|
||||
GIT_EUNMERGED = -10,
|
||||
GIT_ENONFASTFORWARD = -11,
|
||||
GIT_EINVALIDSPEC = -12,
|
||||
@ -67,6 +67,8 @@ typedef enum {
|
||||
GITERR_CHECKOUT,
|
||||
GITERR_FETCHHEAD,
|
||||
GITERR_MERGE,
|
||||
GITERR_SSH,
|
||||
GITERR_FILTER,
|
||||
} git_error_t;
|
||||
|
||||
/**
|
||||
|
||||
142
include/git2/filter.h
Normal file
142
include/git2/filter.h
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef INCLUDE_git_filter_h__
|
||||
#define INCLUDE_git_filter_h__
|
||||
|
||||
#include "common.h"
|
||||
#include "types.h"
|
||||
#include "oid.h"
|
||||
#include "buffer.h"
|
||||
|
||||
/**
|
||||
* @file git2/filter.h
|
||||
* @brief Git filter APIs
|
||||
*
|
||||
* @ingroup Git
|
||||
* @{
|
||||
*/
|
||||
GIT_BEGIN_DECL
|
||||
|
||||
/**
|
||||
* Filters are applied in one of two directions: smudging - which is
|
||||
* exporting a file from the Git object database to the working directory,
|
||||
* and cleaning - which is importing a file from the working directory to
|
||||
* the Git object database. These values control which direction of
|
||||
* change is being applied.
|
||||
*/
|
||||
typedef enum {
|
||||
GIT_FILTER_TO_WORKTREE = 0,
|
||||
GIT_FILTER_SMUDGE = GIT_FILTER_TO_WORKTREE,
|
||||
GIT_FILTER_TO_ODB = 1,
|
||||
GIT_FILTER_CLEAN = GIT_FILTER_TO_ODB,
|
||||
} git_filter_mode_t;
|
||||
|
||||
/**
|
||||
* A filter that can transform file data
|
||||
*
|
||||
* This represents a filter that can be used to transform or even replace
|
||||
* file data. Libgit2 includes one built in filter and it is possible to
|
||||
* write your own (see git2/sys/filter.h for information on that).
|
||||
*
|
||||
* The two builtin filters are:
|
||||
*
|
||||
* * "crlf" which uses the complex rules with the "text", "eol", and
|
||||
* "crlf" file attributes to decide how to convert between LF and CRLF
|
||||
* line endings
|
||||
* * "ident" which replaces "$Id$" in a blob with "$Id: <blob OID>$" upon
|
||||
* checkout and replaced "$Id: <anything>$" with "$Id$" on checkin.
|
||||
*/
|
||||
typedef struct git_filter git_filter;
|
||||
|
||||
/**
|
||||
* List of filters to be applied
|
||||
*
|
||||
* This represents a list of filters to be applied to a file / blob. You
|
||||
* can build the list with one call, apply it with another, and dispose it
|
||||
* with a third. In typical usage, there are not many occasions where a
|
||||
* git_filter_list is needed directly since the library will generally
|
||||
* handle conversions for you, but it can be convenient to be able to
|
||||
* build and apply the list sometimes.
|
||||
*/
|
||||
typedef struct git_filter_list git_filter_list;
|
||||
|
||||
/**
|
||||
* Load the filter list for a given path.
|
||||
*
|
||||
* This will return 0 (success) but set the output git_filter_list to NULL
|
||||
* if no filters are requested for the given file.
|
||||
*
|
||||
* @param filters Output newly created git_filter_list (or NULL)
|
||||
* @param repo Repository object that contains `path`
|
||||
* @param blob The blob to which the filter will be applied (if known)
|
||||
* @param path Relative path of the file to be filtered
|
||||
* @param mode Filtering direction (WT->ODB or ODB->WT)
|
||||
* @return 0 on success (which could still return NULL if no filters are
|
||||
* needed for the requested file), <0 on error
|
||||
*/
|
||||
GIT_EXTERN(int) git_filter_list_load(
|
||||
git_filter_list **filters,
|
||||
git_repository *repo,
|
||||
git_blob *blob, /* can be NULL */
|
||||
const char *path,
|
||||
git_filter_mode_t mode);
|
||||
|
||||
/**
|
||||
* Apply filter list to a data buffer.
|
||||
*
|
||||
* See `git2/buffer.h` for background on `git_buf` objects.
|
||||
*
|
||||
* If the `in` buffer holds data allocated by libgit2 (i.e. `in->asize` is
|
||||
* not zero), then it will be overwritten when applying the filters. If
|
||||
* not, then it will be left untouched.
|
||||
*
|
||||
* If there are no filters to apply (or `filters` is NULL), then the `out`
|
||||
* buffer will reference the `in` buffer data (with `asize` set to zero)
|
||||
* instead of allocating data. This keeps allocations to a minimum, but
|
||||
* it means you have to be careful about freeing the `in` data since `out`
|
||||
* may be pointing to it!
|
||||
*
|
||||
* @param out Buffer to store the result of the filtering
|
||||
* @param filters A loaded git_filter_list (or NULL)
|
||||
* @param in Buffer containing the data to filter
|
||||
* @return 0 on success, an error code otherwise
|
||||
*/
|
||||
GIT_EXTERN(int) git_filter_list_apply_to_data(
|
||||
git_buf *out,
|
||||
git_filter_list *filters,
|
||||
git_buf *in);
|
||||
|
||||
/**
|
||||
* Apply filter list to the contents of a file on disk
|
||||
*/
|
||||
GIT_EXTERN(int) git_filter_list_apply_to_file(
|
||||
git_buf *out,
|
||||
git_filter_list *filters,
|
||||
git_repository *repo,
|
||||
const char *path);
|
||||
|
||||
/**
|
||||
* Apply filter list to the contents of a blob
|
||||
*/
|
||||
GIT_EXTERN(int) git_filter_list_apply_to_blob(
|
||||
git_buf *out,
|
||||
git_filter_list *filters,
|
||||
git_blob *blob);
|
||||
|
||||
/**
|
||||
* Free a git_filter_list
|
||||
*
|
||||
* @param filters A git_filter_list created by `git_filter_list_load`
|
||||
*/
|
||||
GIT_EXTERN(void) git_filter_list_free(git_filter_list *filters);
|
||||
|
||||
|
||||
GIT_END_DECL
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif
|
||||
@ -120,9 +120,9 @@ typedef struct git_index_entry {
|
||||
|
||||
/** Capabilities of system that affect index actions. */
|
||||
typedef enum {
|
||||
GIT_INDEXCAP_IGNORE_CASE = 1,
|
||||
GIT_INDEXCAP_NO_FILEMODE = 2,
|
||||
GIT_INDEXCAP_NO_SYMLINKS = 4,
|
||||
GIT_INDEXCAP_IGNORE_CASE = 1u,
|
||||
GIT_INDEXCAP_NO_FILEMODE = 2u,
|
||||
GIT_INDEXCAP_NO_SYMLINKS = 4u,
|
||||
GIT_INDEXCAP_FROM_OWNER = ~0u
|
||||
} git_indexcap_t;
|
||||
|
||||
|
||||
@ -85,15 +85,15 @@ GIT_EXTERN(int) git_merge_base(
|
||||
*
|
||||
* @param out the OID of a merge base considering all the commits
|
||||
* @param repo the repository where the commits exist
|
||||
* @param input_array oids of the commits
|
||||
* @param length The number of commits in the provided `input_array`
|
||||
* @param input_array oids of the commits
|
||||
* @return Zero on success; GIT_ENOTFOUND or -1 on failure.
|
||||
*/
|
||||
GIT_EXTERN(int) git_merge_base_many(
|
||||
git_oid *out,
|
||||
git_repository *repo,
|
||||
const git_oid input_array[],
|
||||
size_t length);
|
||||
size_t length,
|
||||
const git_oid input_array[]);
|
||||
|
||||
/**
|
||||
* Creates a `git_merge_head` from the given reference
|
||||
|
||||
@ -297,7 +297,7 @@ GIT_EXTERN(int) git_repository_init_ext(
|
||||
* @param out pointer to the reference which will be retrieved
|
||||
* @param repo a repository object
|
||||
*
|
||||
* @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing
|
||||
* @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing
|
||||
* branch, GIT_ENOTFOUND when HEAD is missing; an error code otherwise
|
||||
*/
|
||||
GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo);
|
||||
@ -315,16 +315,16 @@ GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo);
|
||||
GIT_EXTERN(int) git_repository_head_detached(git_repository *repo);
|
||||
|
||||
/**
|
||||
* Check if the current branch is an orphan
|
||||
* Check if the current branch is unborn
|
||||
*
|
||||
* An orphan branch is one named from HEAD but which doesn't exist in
|
||||
* An unborn branch is one named from HEAD but which doesn't exist in
|
||||
* the refs namespace, because it doesn't have any commit to point to.
|
||||
*
|
||||
* @param repo Repo to test
|
||||
* @return 1 if the current branch is an orphan, 0 if it's not; error
|
||||
* @return 1 if the current branch is unborn, 0 if it's not; error
|
||||
* code if there was an error
|
||||
*/
|
||||
GIT_EXTERN(int) git_repository_head_orphan(git_repository *repo);
|
||||
GIT_EXTERN(int) git_repository_head_unborn(git_repository *repo);
|
||||
|
||||
/**
|
||||
* Check if a repository is empty
|
||||
@ -611,7 +611,7 @@ GIT_EXTERN(int) git_repository_set_head_detached(
|
||||
* Otherwise, the HEAD will be detached and point to the peeled Commit.
|
||||
*
|
||||
* @param repo Repository pointer
|
||||
* @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing
|
||||
* @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing
|
||||
* branch or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_repository_detach_head(
|
||||
|
||||
292
include/git2/sys/filter.h
Normal file
292
include/git2/sys/filter.h
Normal file
@ -0,0 +1,292 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef INCLUDE_sys_git_filter_h__
|
||||
#define INCLUDE_sys_git_filter_h__
|
||||
|
||||
#include "git2/filter.h"
|
||||
|
||||
/**
|
||||
* @file git2/sys/filter.h
|
||||
* @brief Git filter backend and plugin routines
|
||||
* @defgroup git_backend Git custom backend APIs
|
||||
* @ingroup Git
|
||||
* @{
|
||||
*/
|
||||
GIT_BEGIN_DECL
|
||||
|
||||
/**
|
||||
* Look up a filter by name
|
||||
*
|
||||
* @param name The name of the filter
|
||||
* @return Pointer to the filter object or NULL if not found
|
||||
*/
|
||||
GIT_EXTERN(git_filter *) git_filter_lookup(const char *name);
|
||||
|
||||
#define GIT_FILTER_CRLF "crlf"
|
||||
#define GIT_FILTER_IDENT "ident"
|
||||
|
||||
/**
|
||||
* This is priority that the internal CRLF filter will be registered with
|
||||
*/
|
||||
#define GIT_FILTER_CRLF_PRIORITY 0
|
||||
|
||||
/**
|
||||
* This is priority that the internal ident filter will be registered with
|
||||
*/
|
||||
#define GIT_FILTER_IDENT_PRIORITY 100
|
||||
|
||||
/**
|
||||
* This is priority to use with a custom filter to imitate a core Git
|
||||
* filter driver, so that it will be run last on checkout and first on
|
||||
* checkin. You do not have to use this, but it helps compatibility.
|
||||
*/
|
||||
#define GIT_FILTER_DRIVER_PRIORITY 200
|
||||
|
||||
/**
|
||||
* Create a new empty filter list
|
||||
*
|
||||
* Normally you won't use this because `git_filter_list_load` will create
|
||||
* the filter list for you, but you can use this in combination with the
|
||||
* `git_filter_lookup` and `git_filter_list_push` functions to assemble
|
||||
* your own chains of filters.
|
||||
*/
|
||||
GIT_EXTERN(int) git_filter_list_new(
|
||||
git_filter_list **out, git_repository *repo, git_filter_mode_t mode);
|
||||
|
||||
/**
|
||||
* Add a filter to a filter list with the given payload.
|
||||
*
|
||||
* Normally you won't have to do this because the filter list is created
|
||||
* by calling the "check" function on registered filters when the filter
|
||||
* attributes are set, but this does allow more direct manipulation of
|
||||
* filter lists when desired.
|
||||
*
|
||||
* Note that normally the "check" function can set up a payload for the
|
||||
* filter. Using this function, you can either pass in a payload if you
|
||||
* know the expected payload format, or you can pass NULL. Some filters
|
||||
* may fail with a NULL payload. Good luck!
|
||||
*/
|
||||
GIT_EXTERN(int) git_filter_list_push(
|
||||
git_filter_list *fl, git_filter *filter, void *payload);
|
||||
|
||||
/**
|
||||
* Look up how many filters are in the list
|
||||
*
|
||||
* We will attempt to apply all of these filters to any data passed in,
|
||||
* but note that the filter apply action still has the option of skipping
|
||||
* data that is passed in (for example, the CRLF filter will skip data
|
||||
* that appears to be binary).
|
||||
*
|
||||
* @param fl A filter list
|
||||
* @return The number of filters in the list
|
||||
*/
|
||||
GIT_EXTERN(size_t) git_filter_list_length(const git_filter_list *fl);
|
||||
|
||||
/**
|
||||
* A filter source represents a file/blob to be processed
|
||||
*/
|
||||
typedef struct git_filter_source git_filter_source;
|
||||
|
||||
/**
|
||||
* Get the repository that the source data is coming from.
|
||||
*/
|
||||
GIT_EXTERN(git_repository *) git_filter_source_repo(const git_filter_source *src);
|
||||
|
||||
/**
|
||||
* Get the path that the source data is coming from.
|
||||
*/
|
||||
GIT_EXTERN(const char *) git_filter_source_path(const git_filter_source *src);
|
||||
|
||||
/**
|
||||
* Get the file mode of the source file
|
||||
* If the mode is unknown, this will return 0
|
||||
*/
|
||||
GIT_EXTERN(uint16_t) git_filter_source_filemode(const git_filter_source *src);
|
||||
|
||||
/**
|
||||
* Get the OID of the source
|
||||
* If the OID is unknown (often the case with GIT_FILTER_CLEAN) then
|
||||
* this will return NULL.
|
||||
*/
|
||||
GIT_EXTERN(const git_oid *) git_filter_source_id(const git_filter_source *src);
|
||||
|
||||
/**
|
||||
* Get the git_filter_mode_t to be applied
|
||||
*/
|
||||
GIT_EXTERN(git_filter_mode_t) git_filter_source_mode(const git_filter_source *src);
|
||||
|
||||
/*
|
||||
* struct git_filter
|
||||
*
|
||||
* The filter lifecycle:
|
||||
* - initialize - first use of filter
|
||||
* - shutdown - filter removed/unregistered from system
|
||||
* - check - considering filter for file
|
||||
* - apply - apply filter to file contents
|
||||
* - cleanup - done with file
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initialize callback on filter
|
||||
*
|
||||
* Specified as `filter.initialize`, this is an optional callback invoked
|
||||
* before a filter is first used. It will be called once at most.
|
||||
*
|
||||
* If non-NULL, the filter's `initialize` callback will be invoked right
|
||||
* before the first use of the filter, so you can defer expensive
|
||||
* initialization operations (in case libgit2 is being used in a way that
|
||||
* doesn't need the filter).
|
||||
*/
|
||||
typedef int (*git_filter_init_fn)(git_filter *self);
|
||||
|
||||
/**
|
||||
* Shutdown callback on filter
|
||||
*
|
||||
* Specified as `filter.shutdown`, this is an optional callback invoked
|
||||
* when the filter is unregistered or when libgit2 is shutting down. It
|
||||
* will be called once at most and should release resources as needed.
|
||||
*
|
||||
* Typically this function will free the `git_filter` object itself.
|
||||
*/
|
||||
typedef void (*git_filter_shutdown_fn)(git_filter *self);
|
||||
|
||||
/**
|
||||
* Callback to decide if a given source needs this filter
|
||||
*
|
||||
* Specified as `filter.check`, this is an optional callback that checks
|
||||
* if filtering is needed for a given source.
|
||||
*
|
||||
* It should return 0 if the filter should be applied (i.e. success),
|
||||
* GIT_PASSTHROUGH if the filter should not be applied, or an error code
|
||||
* to fail out of the filter processing pipeline and return to the caller.
|
||||
*
|
||||
* The `attr_values` will be set to the values of any attributes given in
|
||||
* the filter definition. See `git_filter` below for more detail.
|
||||
*
|
||||
* The `payload` will be a pointer to a reference payload for the filter.
|
||||
* This will start as NULL, but `check` can assign to this pointer for
|
||||
* later use by the `apply` callback. Note that the value should be heap
|
||||
* allocated (not stack), so that it doesn't go away before the `apply`
|
||||
* callback can use it. If a filter allocates and assigns a value to the
|
||||
* `payload`, it will need a `cleanup` callback to free the payload.
|
||||
*/
|
||||
typedef int (*git_filter_check_fn)(
|
||||
git_filter *self,
|
||||
void **payload, /* points to NULL ptr on entry, may be set */
|
||||
const git_filter_source *src,
|
||||
const char **attr_values);
|
||||
|
||||
/**
|
||||
* Callback to actually perform the data filtering
|
||||
*
|
||||
* Specified as `filter.apply`, this is the callback that actually filters
|
||||
* data. If it successfully writes the output, it should return 0. Like
|
||||
* `check`, it can return GIT_PASSTHROUGH to indicate that the filter
|
||||
* doesn't want to run. Other error codes will stop filter processing and
|
||||
* return to the caller.
|
||||
*
|
||||
* The `payload` value will refer to any payload that was set by the
|
||||
* `check` callback. It may be read from or written to as needed.
|
||||
*/
|
||||
typedef int (*git_filter_apply_fn)(
|
||||
git_filter *self,
|
||||
void **payload, /* may be read and/or set */
|
||||
git_buf *to,
|
||||
const git_buf *from,
|
||||
const git_filter_source *src);
|
||||
|
||||
/**
|
||||
* Callback to clean up after filtering has been applied
|
||||
*
|
||||
* Specified as `filter.cleanup`, this is an optional callback invoked
|
||||
* after the filter has been applied. If the `check` or `apply` callbacks
|
||||
* allocated a `payload` to keep per-source filter state, use this
|
||||
* callback to free that payload and release resources as required.
|
||||
*/
|
||||
typedef void (*git_filter_cleanup_fn)(
|
||||
git_filter *self,
|
||||
void *payload);
|
||||
|
||||
/**
|
||||
* Filter structure used to register custom filters.
|
||||
*
|
||||
* To associate extra data with a filter, allocate extra data and put the
|
||||
* `git_filter` struct at the start of your data buffer, then cast the
|
||||
* `self` pointer to your larger structure when your callback is invoked.
|
||||
*
|
||||
* `version` should be set to GIT_FILTER_VERSION
|
||||
*
|
||||
* `attributes` is a whitespace-separated list of attribute names to check
|
||||
* for this filter (e.g. "eol crlf text"). If the attribute name is bare,
|
||||
* it will be simply loaded and passed to the `check` callback. If it has
|
||||
* a value (i.e. "name=value"), the attribute must match that value for
|
||||
* the filter to be applied.
|
||||
*
|
||||
* The `initialize`, `shutdown`, `check`, `apply`, and `cleanup` callbacks
|
||||
* are all documented above with the respective function pointer typedefs.
|
||||
*/
|
||||
struct git_filter {
|
||||
unsigned int version;
|
||||
|
||||
const char *attributes;
|
||||
|
||||
git_filter_init_fn initialize;
|
||||
git_filter_shutdown_fn shutdown;
|
||||
git_filter_check_fn check;
|
||||
git_filter_apply_fn apply;
|
||||
git_filter_cleanup_fn cleanup;
|
||||
};
|
||||
|
||||
#define GIT_FILTER_VERSION 1
|
||||
|
||||
/**
|
||||
* Register a filter under a given name with a given priority.
|
||||
*
|
||||
* As mentioned elsewhere, the initialize callback will not be invoked
|
||||
* immediately. It is deferred until the filter is used in some way.
|
||||
*
|
||||
* A filter's attribute checks and `check` and `apply` callbacks will be
|
||||
* issued in order of `priority` on smudge (to workdir), and in reverse
|
||||
* order of `priority` on clean (to odb).
|
||||
*
|
||||
* Two filters are preregistered with libgit2:
|
||||
* - GIT_FILTER_CRLF with priority 0
|
||||
* - GIT_FILTER_IDENT with priority 100
|
||||
*
|
||||
* Currently the filter registry is not thread safe, so any registering or
|
||||
* deregistering of filters must be done outside of any possible usage of
|
||||
* the filters (i.e. during application setup or shutdown).
|
||||
*
|
||||
* @param name A name by which the filter can be referenced. Attempting
|
||||
* to register with an in-use name will return GIT_EEXISTS.
|
||||
* @param filter The filter definition. This pointer will be stored as is
|
||||
* by libgit2 so it must be a durable allocation (either static
|
||||
* or on the heap).
|
||||
* @param priority The priority for filter application
|
||||
* @return 0 on successful registry, error code <0 on failure
|
||||
*/
|
||||
GIT_EXTERN(int) git_filter_register(
|
||||
const char *name, git_filter *filter, int priority);
|
||||
|
||||
/**
|
||||
* Remove the filter with the given name
|
||||
*
|
||||
* Attempting to remove the builtin libgit2 filters is not permitted and
|
||||
* will return an error.
|
||||
*
|
||||
* Currently the filter registry is not thread safe, so any registering or
|
||||
* deregistering of filters must be done outside of any possible usage of
|
||||
* the filters (i.e. during application setup or shutdown).
|
||||
*
|
||||
* @param name The name under which the filter was registered
|
||||
* @return 0 on success, error code <0 on failure
|
||||
*/
|
||||
GIT_EXTERN(int) git_filter_unregister(const char *name);
|
||||
|
||||
/** @} */
|
||||
GIT_END_DECL
|
||||
#endif
|
||||
@ -103,7 +103,7 @@ struct git_refdb_backend {
|
||||
* Deletes the given reference from the refdb. A refdb implementation
|
||||
* must provide this function.
|
||||
*/
|
||||
int (*delete)(git_refdb_backend *backend, const char *ref_name);
|
||||
int (*del)(git_refdb_backend *backend, const char *ref_name);
|
||||
|
||||
/**
|
||||
* Suggests that the given refdb compress or optimize its references.
|
||||
@ -121,8 +121,8 @@ struct git_refdb_backend {
|
||||
void (*free)(git_refdb_backend *backend);
|
||||
};
|
||||
|
||||
#define GIT_ODB_BACKEND_VERSION 1
|
||||
#define GIT_ODB_BACKEND_INIT {GIT_ODB_BACKEND_VERSION}
|
||||
#define GIT_REFDB_BACKEND_VERSION 1
|
||||
#define GIT_REFDB_BACKEND_INIT {GIT_REFDB_BACKEND_VERSION}
|
||||
|
||||
/**
|
||||
* Constructors for default filesystem-based refdb backend
|
||||
|
||||
@ -59,7 +59,7 @@ GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size)
|
||||
#define git_array_alloc(a) \
|
||||
((a).size >= (a).asize) ? \
|
||||
git_array_grow(&(a), sizeof(*(a).ptr)) : \
|
||||
(a).ptr ? &(a).ptr[(a).size++] : NULL
|
||||
((a).ptr ? &(a).ptr[(a).size++] : NULL)
|
||||
|
||||
#define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : NULL)
|
||||
|
||||
|
||||
@ -26,7 +26,6 @@ git_attr_t git_attr_value(const char *attr)
|
||||
return GIT_ATTR_VALUE_T;
|
||||
}
|
||||
|
||||
|
||||
static int collect_attr_files(
|
||||
git_repository *repo,
|
||||
uint32_t flags,
|
||||
@ -103,8 +102,6 @@ int git_attr_get_many(
|
||||
attr_get_many_info *info = NULL;
|
||||
size_t num_found = 0;
|
||||
|
||||
memset((void *)values, 0, sizeof(const char *) * num_attr);
|
||||
|
||||
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
|
||||
return -1;
|
||||
|
||||
@ -141,6 +138,11 @@ int git_attr_get_many(
|
||||
}
|
||||
}
|
||||
|
||||
for (k = 0; k < num_attr; k++) {
|
||||
if (!info[k].found)
|
||||
values[k] = NULL;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
git_vector_free(&files);
|
||||
git_attr_path__free(&path);
|
||||
|
||||
@ -39,7 +39,7 @@ int git_attr_file__new(
|
||||
attrs->key = git_pool_malloc(attrs->pool, (uint32_t)len + 3);
|
||||
GITERR_CHECK_ALLOC(attrs->key);
|
||||
|
||||
attrs->key[0] = '0' + from;
|
||||
attrs->key[0] = '0' + (char)from;
|
||||
attrs->key[1] = '#';
|
||||
memcpy(&attrs->key[2], path, len);
|
||||
attrs->key[len + 2] = '\0';
|
||||
|
||||
76
src/blob.c
76
src/blob.c
@ -108,29 +108,21 @@ static int write_file_filtered(
|
||||
git_off_t *size,
|
||||
git_odb *odb,
|
||||
const char *full_path,
|
||||
git_vector *filters)
|
||||
git_filter_list *fl)
|
||||
{
|
||||
int error;
|
||||
git_buf source = GIT_BUF_INIT;
|
||||
git_buf dest = GIT_BUF_INIT;
|
||||
git_buf tgt = GIT_BUF_INIT;
|
||||
|
||||
if ((error = git_futils_readbuffer(&source, full_path)) < 0)
|
||||
return error;
|
||||
|
||||
error = git_filters_apply(&dest, &source, filters);
|
||||
|
||||
/* Free the source as soon as possible. This can be big in memory,
|
||||
* and we don't want to ODB write to choke */
|
||||
git_buf_free(&source);
|
||||
error = git_filter_list_apply_to_file(&tgt, fl, NULL, full_path);
|
||||
|
||||
/* Write the file to disk if it was properly filtered */
|
||||
if (!error) {
|
||||
*size = dest.size;
|
||||
*size = tgt.size;
|
||||
|
||||
error = git_odb_write(oid, odb, dest.ptr, dest.size, GIT_OBJ_BLOB);
|
||||
error = git_odb_write(oid, odb, tgt.ptr, tgt.size, GIT_OBJ_BLOB);
|
||||
}
|
||||
|
||||
git_buf_free(&dest);
|
||||
git_buf_free(&tgt);
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -198,29 +190,25 @@ int git_blob__create_from_paths(
|
||||
if (S_ISLNK(mode)) {
|
||||
error = write_symlink(oid, odb, content_path, (size_t)size);
|
||||
} else {
|
||||
git_vector write_filters = GIT_VECTOR_INIT;
|
||||
int filter_count = 0;
|
||||
git_filter_list *fl = NULL;
|
||||
|
||||
if (try_load_filters) {
|
||||
if (try_load_filters)
|
||||
/* Load the filters for writing this file to the ODB */
|
||||
filter_count = git_filters_load(
|
||||
&write_filters, repo, hint_path, GIT_FILTER_TO_ODB);
|
||||
}
|
||||
error = git_filter_list_load(
|
||||
&fl, repo, NULL, hint_path, GIT_FILTER_TO_ODB);
|
||||
|
||||
if (filter_count < 0) {
|
||||
/* Negative value means there was a critical error */
|
||||
error = filter_count;
|
||||
} else if (filter_count == 0) {
|
||||
if (error < 0)
|
||||
/* well, that didn't work */;
|
||||
else if (fl == NULL)
|
||||
/* No filters need to be applied to the document: we can stream
|
||||
* directly from disk */
|
||||
error = write_file_stream(oid, odb, content_path, size);
|
||||
} else {
|
||||
else {
|
||||
/* We need to apply one or more filters */
|
||||
error = write_file_filtered(
|
||||
oid, &size, odb, content_path, &write_filters);
|
||||
}
|
||||
error = write_file_filtered(oid, &size, odb, content_path, fl);
|
||||
|
||||
git_filters_free(&write_filters);
|
||||
git_filter_list_free(fl);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: eventually support streaming filtered files, for files
|
||||
@ -333,8 +321,34 @@ int git_blob_is_binary(git_blob *blob)
|
||||
|
||||
assert(blob);
|
||||
|
||||
content.ptr = blob->odb_object->buffer;
|
||||
content.size = min(blob->odb_object->cached.size, 4000);
|
||||
content.ptr = blob->odb_object->buffer;
|
||||
content.size = min(blob->odb_object->cached.size, 4000);
|
||||
content.asize = 0;
|
||||
|
||||
return git_buf_text_is_binary(&content);
|
||||
}
|
||||
|
||||
int git_blob_filtered_content(
|
||||
git_buf *out,
|
||||
git_blob *blob,
|
||||
const char *path,
|
||||
int check_for_binary_data)
|
||||
{
|
||||
int error = 0;
|
||||
git_filter_list *fl = NULL;
|
||||
|
||||
assert(blob && path && out);
|
||||
|
||||
if (check_for_binary_data && git_blob_is_binary(blob))
|
||||
return 0;
|
||||
|
||||
if (!(error = git_filter_list_load(
|
||||
&fl, git_blob_owner(blob), blob, path, GIT_FILTER_TO_WORKTREE))) {
|
||||
|
||||
error = git_filter_list_apply_to_blob(out, fl, blob);
|
||||
|
||||
git_filter_list_free(fl);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -585,7 +585,7 @@ int git_branch_is_head(
|
||||
|
||||
error = git_repository_head(&head, git_reference_owner(branch));
|
||||
|
||||
if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND)
|
||||
if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND)
|
||||
return false;
|
||||
|
||||
if (error < 0)
|
||||
|
||||
@ -70,10 +70,10 @@ int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src)
|
||||
assert(tgt != src);
|
||||
|
||||
if (!next)
|
||||
return GIT_ENOTFOUND;
|
||||
return git_buf_set(tgt, src->ptr, src->size);
|
||||
|
||||
/* reduce reallocs while in the loop */
|
||||
if (git_buf_grow(tgt, src->size) < 0)
|
||||
if (git_buf_grow(tgt, src->size + 1) < 0)
|
||||
return -1;
|
||||
out = tgt->ptr;
|
||||
tgt->size = 0;
|
||||
@ -81,20 +81,25 @@ int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src)
|
||||
/* Find the next \r and copy whole chunk up to there to tgt */
|
||||
for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) {
|
||||
if (next > scan) {
|
||||
size_t copylen = next - scan;
|
||||
size_t copylen = (size_t)(next - scan);
|
||||
memcpy(out, scan, copylen);
|
||||
out += copylen;
|
||||
}
|
||||
|
||||
/* Do not drop \r unless it is followed by \n */
|
||||
if (next[1] != '\n')
|
||||
if (next + 1 == scan_end || next[1] != '\n')
|
||||
*out++ = '\r';
|
||||
}
|
||||
|
||||
/* Copy remaining input into dest */
|
||||
memcpy(out, scan, scan_end - scan + 1); /* +1 for NUL byte */
|
||||
out += (scan_end - scan);
|
||||
tgt->size = out - tgt->ptr;
|
||||
if (scan < scan_end) {
|
||||
size_t remaining = (size_t)(scan_end - scan);
|
||||
memcpy(out, scan, remaining);
|
||||
out += remaining;
|
||||
}
|
||||
|
||||
tgt->size = (size_t)(out - tgt->ptr);
|
||||
tgt->ptr[tgt->size] = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -109,7 +114,7 @@ int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src)
|
||||
assert(tgt != src);
|
||||
|
||||
if (!next)
|
||||
return GIT_ENOTFOUND;
|
||||
return git_buf_set(tgt, src->ptr, src->size);
|
||||
|
||||
/* attempt to reduce reallocs while in the loop */
|
||||
if (git_buf_grow(tgt, src->size + (src->size >> 4) + 1) < 0)
|
||||
|
||||
@ -56,16 +56,16 @@ GIT_INLINE(int) git_buf_text_puts_escape_regex(git_buf *buf, const char *string)
|
||||
extern void git_buf_text_unescape(git_buf *buf);
|
||||
|
||||
/**
|
||||
* Replace all \r\n with \n (or do nothing if no \r\n are found)
|
||||
* Replace all \r\n with \n. Does not modify \r without trailing \n.
|
||||
*
|
||||
* @return 0 on success, GIT_ENOTFOUND if no \r\n, -1 on memory error
|
||||
* @return 0 on success, -1 on memory error
|
||||
*/
|
||||
extern int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src);
|
||||
|
||||
/**
|
||||
* Replace all \n with \r\n (or do nothing if no \n are found)
|
||||
* Replace all \n with \r\n. Does not modify existing \r\n.
|
||||
*
|
||||
* @return 0 on success, GIT_ENOTFOUND if no \n, -1 on memory error
|
||||
* @return 0 on success, -1 on memory error
|
||||
*/
|
||||
extern int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src);
|
||||
|
||||
|
||||
27
src/buffer.c
27
src/buffer.c
@ -6,6 +6,7 @@
|
||||
*/
|
||||
#include "buffer.h"
|
||||
#include "posix.h"
|
||||
#include "git2/buffer.h"
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
|
||||
@ -31,7 +32,8 @@ void git_buf_init(git_buf *buf, size_t initial_size)
|
||||
git_buf_grow(buf, initial_size);
|
||||
}
|
||||
|
||||
int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom)
|
||||
int git_buf_try_grow(
|
||||
git_buf *buf, size_t target_size, bool mark_oom, bool preserve_external)
|
||||
{
|
||||
char *new_ptr;
|
||||
size_t new_size;
|
||||
@ -39,6 +41,9 @@ int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom)
|
||||
if (buf->ptr == git_buf__oom)
|
||||
return -1;
|
||||
|
||||
if (!target_size)
|
||||
target_size = buf->size;
|
||||
|
||||
if (target_size <= buf->asize)
|
||||
return 0;
|
||||
|
||||
@ -66,6 +71,9 @@ int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (preserve_external && !buf->asize && buf->ptr != NULL && buf->size > 0)
|
||||
memcpy(new_ptr, buf->ptr, min(buf->size, new_size));
|
||||
|
||||
buf->asize = new_size;
|
||||
buf->ptr = new_ptr;
|
||||
|
||||
@ -77,11 +85,16 @@ int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_buf_grow(git_buf *buffer, size_t target_size)
|
||||
{
|
||||
return git_buf_try_grow(buffer, target_size, true, true);
|
||||
}
|
||||
|
||||
void git_buf_free(git_buf *buf)
|
||||
{
|
||||
if (!buf) return;
|
||||
|
||||
if (buf->ptr != git_buf__initbuf && buf->ptr != git_buf__oom)
|
||||
if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_buf__oom)
|
||||
git__free(buf->ptr);
|
||||
|
||||
git_buf_init(buf, 0);
|
||||
@ -90,11 +103,15 @@ void git_buf_free(git_buf *buf)
|
||||
void git_buf_clear(git_buf *buf)
|
||||
{
|
||||
buf->size = 0;
|
||||
|
||||
if (!buf->ptr)
|
||||
buf->ptr = git_buf__initbuf;
|
||||
|
||||
if (buf->asize > 0)
|
||||
buf->ptr[0] = '\0';
|
||||
}
|
||||
|
||||
int git_buf_set(git_buf *buf, const char *data, size_t len)
|
||||
int git_buf_set(git_buf *buf, const void *data, size_t len)
|
||||
{
|
||||
if (len == 0 || data == NULL) {
|
||||
git_buf_clear(buf);
|
||||
@ -137,7 +154,7 @@ int git_buf_puts(git_buf *buf, const char *string)
|
||||
return git_buf_put(buf, string, strlen(string));
|
||||
}
|
||||
|
||||
static const char b64str[64] =
|
||||
static const char b64str[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
int git_buf_put_base64(git_buf *buf, const char *data, size_t len)
|
||||
@ -194,6 +211,8 @@ int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
|
||||
format, args
|
||||
);
|
||||
|
||||
va_end(args);
|
||||
|
||||
if (len < 0) {
|
||||
git__free(buf->ptr);
|
||||
buf->ptr = git_buf__oom;
|
||||
|
||||
40
src/buffer.h
40
src/buffer.h
@ -9,18 +9,26 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "git2/strarray.h"
|
||||
#include "git2/buffer.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
typedef struct {
|
||||
char *ptr;
|
||||
size_t asize, size;
|
||||
} git_buf;
|
||||
/* typedef struct {
|
||||
* char *ptr;
|
||||
* size_t asize, size;
|
||||
* } git_buf;
|
||||
*/
|
||||
|
||||
extern char git_buf__initbuf[];
|
||||
extern char git_buf__oom[];
|
||||
|
||||
/* Use to initialize buffer structure when git_buf is on stack */
|
||||
#define GIT_BUF_INIT { git_buf__initbuf, 0, 0 }
|
||||
|
||||
GIT_INLINE(bool) git_buf_is_allocated(const git_buf *buf)
|
||||
{
|
||||
return (buf->ptr != NULL && buf->asize > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a git_buf structure.
|
||||
*
|
||||
@ -32,27 +40,16 @@ extern void git_buf_init(git_buf *buf, size_t initial_size);
|
||||
/**
|
||||
* Attempt to grow the buffer to hold at least `target_size` bytes.
|
||||
*
|
||||
* If the allocation fails, this will return an error. If mark_oom is true,
|
||||
* If the allocation fails, this will return an error. If `mark_oom` is true,
|
||||
* this will mark the buffer as invalid for future operations; if false,
|
||||
* existing buffer content will be preserved, but calling code must handle
|
||||
* that buffer was not expanded.
|
||||
* that buffer was not expanded. If `preserve_external` is true, then any
|
||||
* existing data pointed to be `ptr` even if `asize` is zero will be copied
|
||||
* into the newly allocated buffer.
|
||||
*/
|
||||
extern int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom);
|
||||
extern int git_buf_try_grow(
|
||||
git_buf *buf, size_t target_size, bool mark_oom, bool preserve_external);
|
||||
|
||||
/**
|
||||
* Grow the buffer to hold at least `target_size` bytes.
|
||||
*
|
||||
* If the allocation fails, this will return an error and the buffer will be
|
||||
* marked as invalid for future operations, invaliding contents.
|
||||
*
|
||||
* @return 0 on success or -1 on failure
|
||||
*/
|
||||
GIT_INLINE(int) git_buf_grow(git_buf *buf, size_t target_size)
|
||||
{
|
||||
return git_buf_try_grow(buf, target_size, true);
|
||||
}
|
||||
|
||||
extern void git_buf_free(git_buf *buf);
|
||||
extern void git_buf_swap(git_buf *buf_a, git_buf *buf_b);
|
||||
extern char *git_buf_detach(git_buf *buf);
|
||||
extern void git_buf_attach(git_buf *buf, char *ptr, size_t asize);
|
||||
@ -81,7 +78,6 @@ GIT_INLINE(bool) git_buf_oom(const git_buf *buf)
|
||||
* return code of these functions and call them in a series then just call
|
||||
* git_buf_oom at the end.
|
||||
*/
|
||||
int git_buf_set(git_buf *buf, const char *data, size_t len);
|
||||
int git_buf_sets(git_buf *buf, const char *string);
|
||||
int git_buf_putc(git_buf *buf, char c);
|
||||
int git_buf_put(git_buf *buf, const char *data, size_t len);
|
||||
|
||||
@ -678,7 +678,7 @@ fail:
|
||||
|
||||
static int buffer_to_file(
|
||||
struct stat *st,
|
||||
git_buf *buffer,
|
||||
git_buf *buf,
|
||||
const char *path,
|
||||
mode_t dir_mode,
|
||||
int file_open_flags,
|
||||
@ -690,7 +690,7 @@ static int buffer_to_file(
|
||||
return error;
|
||||
|
||||
if ((error = git_futils_writebuffer(
|
||||
buffer, path, file_open_flags, file_mode)) < 0)
|
||||
buf, path, file_open_flags, file_mode)) < 0)
|
||||
return error;
|
||||
|
||||
if (st != NULL && (error = p_stat(path, st)) < 0)
|
||||
@ -710,57 +710,28 @@ static int blob_content_to_file(
|
||||
mode_t entry_filemode,
|
||||
git_checkout_opts *opts)
|
||||
{
|
||||
int error = -1, nb_filters = 0;
|
||||
mode_t file_mode = opts->file_mode;
|
||||
bool dont_free_filtered;
|
||||
git_buf unfiltered = GIT_BUF_INIT, filtered = GIT_BUF_INIT;
|
||||
git_vector filters = GIT_VECTOR_INIT;
|
||||
int error = 0;
|
||||
mode_t file_mode = opts->file_mode ? opts->file_mode : entry_filemode;
|
||||
git_buf out = GIT_BUF_INIT;
|
||||
git_filter_list *fl = NULL;
|
||||
|
||||
/* Create a fake git_buf from the blob raw data... */
|
||||
filtered.ptr = (void *)git_blob_rawcontent(blob);
|
||||
filtered.size = (size_t)git_blob_rawsize(blob);
|
||||
/* ... and make sure it doesn't get unexpectedly freed */
|
||||
dont_free_filtered = true;
|
||||
|
||||
if (!opts->disable_filters &&
|
||||
!git_buf_text_is_binary(&filtered) &&
|
||||
(nb_filters = git_filters_load(
|
||||
&filters,
|
||||
git_object_owner((git_object *)blob),
|
||||
path,
|
||||
GIT_FILTER_TO_WORKTREE)) > 0)
|
||||
{
|
||||
/* reset 'filtered' so it can be a filter target */
|
||||
git_buf_init(&filtered, 0);
|
||||
dont_free_filtered = false;
|
||||
}
|
||||
|
||||
if (nb_filters < 0)
|
||||
return nb_filters;
|
||||
|
||||
if (nb_filters > 0) {
|
||||
if ((error = git_blob__getbuf(&unfiltered, blob)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if ((error = git_filters_apply(&filtered, &unfiltered, &filters)) < 0)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Allow overriding of file mode */
|
||||
if (!file_mode)
|
||||
file_mode = entry_filemode;
|
||||
|
||||
error = buffer_to_file(
|
||||
st, &filtered, path, opts->dir_mode, opts->file_open_flags, file_mode);
|
||||
if (!opts->disable_filters)
|
||||
error = git_filter_list_load(
|
||||
&fl, git_blob_owner(blob), blob, path, GIT_FILTER_TO_WORKTREE);
|
||||
|
||||
if (!error)
|
||||
error = git_filter_list_apply_to_blob(&out, fl, blob);
|
||||
|
||||
git_filter_list_free(fl);
|
||||
|
||||
if (!error) {
|
||||
error = buffer_to_file(
|
||||
st, &out, path, opts->dir_mode, opts->file_open_flags, file_mode);
|
||||
|
||||
st->st_mode = entry_filemode;
|
||||
|
||||
cleanup:
|
||||
git_filters_free(&filters);
|
||||
git_buf_free(&unfiltered);
|
||||
if (!dont_free_filtered)
|
||||
git_buf_free(&filtered);
|
||||
git_buf_free(&out);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
@ -1232,7 +1203,7 @@ static int checkout_data_init(
|
||||
|
||||
error = checkout_lookup_head_tree(&data->opts.baseline, repo);
|
||||
|
||||
if (error == GIT_EORPHANEDHEAD) {
|
||||
if (error == GIT_EUNBORNBRANCH) {
|
||||
error = 0;
|
||||
giterr_clear();
|
||||
}
|
||||
|
||||
@ -415,7 +415,7 @@ static bool should_checkout(
|
||||
if (opts->checkout_strategy == GIT_CHECKOUT_NONE)
|
||||
return false;
|
||||
|
||||
return !git_repository_head_orphan(repo);
|
||||
return !git_repository_head_unborn(repo);
|
||||
}
|
||||
|
||||
static void normalize_options(git_clone_options *dst, const git_clone_options *src, git_repository_init_options *initOptions)
|
||||
@ -427,8 +427,7 @@ static void normalize_options(git_clone_options *dst, const git_clone_options *s
|
||||
|
||||
/* Provide defaults for null pointers */
|
||||
if (!dst->remote_name) dst->remote_name = "origin";
|
||||
if (!dst->init_options)
|
||||
{
|
||||
if (!dst->init_options) {
|
||||
dst->init_options = initOptions;
|
||||
initOptions->flags = GIT_REPOSITORY_INIT_MKPATH;
|
||||
if (dst->bare)
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
#include "git2/sys/config.h"
|
||||
#include "git2/types.h"
|
||||
#include "strmap.h"
|
||||
#include "array.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <sys/types.h>
|
||||
@ -25,6 +26,7 @@ GIT__USE_STRMAP;
|
||||
typedef struct cvar_t {
|
||||
struct cvar_t *next;
|
||||
git_config_entry *entry;
|
||||
int included; /* whether this is part of [include] */
|
||||
} cvar_t;
|
||||
|
||||
typedef struct git_config_file_iter {
|
||||
@ -33,6 +35,8 @@ typedef struct git_config_file_iter {
|
||||
cvar_t* next_var;
|
||||
} git_config_file_iter;
|
||||
|
||||
/* Max depth for [include] directives */
|
||||
#define MAX_INCLUDE_DEPTH 10
|
||||
|
||||
#define CVAR_LIST_HEAD(list) ((list)->head)
|
||||
|
||||
@ -72,34 +76,37 @@ typedef struct git_config_file_iter {
|
||||
(iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\
|
||||
(iter) = (tmp))
|
||||
|
||||
struct reader {
|
||||
time_t file_mtime;
|
||||
size_t file_size;
|
||||
char *file_path;
|
||||
git_buf buffer;
|
||||
char *read_ptr;
|
||||
int line_number;
|
||||
int eof;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
git_config_backend parent;
|
||||
|
||||
git_strmap *values;
|
||||
|
||||
struct {
|
||||
git_buf buffer;
|
||||
char *read_ptr;
|
||||
int line_number;
|
||||
int eof;
|
||||
} reader;
|
||||
git_array_t(struct reader) readers;
|
||||
|
||||
char *file_path;
|
||||
time_t file_mtime;
|
||||
size_t file_size;
|
||||
|
||||
git_config_level_t level;
|
||||
} diskfile_backend;
|
||||
|
||||
static int config_parse(diskfile_backend *cfg_file, git_config_level_t level);
|
||||
static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value);
|
||||
static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth);
|
||||
static int parse_variable(struct reader *reader, char **var_name, char **var_value);
|
||||
static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
|
||||
static char *escape_value(const char *ptr);
|
||||
|
||||
static void set_parse_error(diskfile_backend *backend, int col, const char *error_str)
|
||||
static void set_parse_error(struct reader *reader, int col, const char *error_str)
|
||||
{
|
||||
giterr_set(GITERR_CONFIG, "Failed to parse config file: %s (in %s:%d, column %d)",
|
||||
error_str, backend->file_path, backend->reader.line_number, col);
|
||||
error_str, reader->file_path, reader->line_number, col);
|
||||
}
|
||||
|
||||
static void cvar_free(cvar_t *var)
|
||||
@ -125,7 +132,7 @@ int git_config_file_normalize_section(char *start, char *end)
|
||||
if (end && scan >= end)
|
||||
break;
|
||||
if (isalnum(*scan))
|
||||
*scan = tolower(*scan);
|
||||
*scan = (char)tolower(*scan);
|
||||
else if (*scan != '-' || scan == start)
|
||||
return GIT_EINVALIDSPEC;
|
||||
}
|
||||
@ -156,6 +163,7 @@ static void free_vars(git_strmap *values)
|
||||
static int config_open(git_config_backend *cfg, git_config_level_t level)
|
||||
{
|
||||
int res;
|
||||
struct reader *reader;
|
||||
diskfile_backend *b = (diskfile_backend *)cfg;
|
||||
|
||||
b->level = level;
|
||||
@ -163,32 +171,52 @@ static int config_open(git_config_backend *cfg, git_config_level_t level)
|
||||
b->values = git_strmap_alloc();
|
||||
GITERR_CHECK_ALLOC(b->values);
|
||||
|
||||
git_buf_init(&b->reader.buffer, 0);
|
||||
git_array_init(b->readers);
|
||||
reader = git_array_alloc(b->readers);
|
||||
memset(reader, 0, sizeof(struct reader));
|
||||
|
||||
reader->file_path = git__strdup(b->file_path);
|
||||
GITERR_CHECK_ALLOC(reader->file_path);
|
||||
|
||||
git_buf_init(&reader->buffer, 0);
|
||||
res = git_futils_readbuffer_updated(
|
||||
&b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, NULL);
|
||||
&reader->buffer, b->file_path, &reader->file_mtime, &reader->file_size, NULL);
|
||||
|
||||
/* It's fine if the file doesn't exist */
|
||||
if (res == GIT_ENOTFOUND)
|
||||
return 0;
|
||||
|
||||
if (res < 0 || (res = config_parse(b, level)) < 0) {
|
||||
if (res < 0 || (res = config_parse(b, reader, level, 0)) < 0) {
|
||||
free_vars(b->values);
|
||||
b->values = NULL;
|
||||
}
|
||||
|
||||
git_buf_free(&b->reader.buffer);
|
||||
reader = git_array_get(b->readers, 0);
|
||||
git_buf_free(&reader->buffer);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int config_refresh(git_config_backend *cfg)
|
||||
{
|
||||
int res, updated = 0;
|
||||
int res = 0, updated = 0, any_updated = 0;
|
||||
diskfile_backend *b = (diskfile_backend *)cfg;
|
||||
git_strmap *old_values;
|
||||
struct reader *reader;
|
||||
uint32_t i;
|
||||
|
||||
res = git_futils_readbuffer_updated(
|
||||
&b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, &updated);
|
||||
if (res < 0 || !updated)
|
||||
for (i = 0; i < git_array_size(b->readers); i++) {
|
||||
reader = git_array_get(b->readers, i);
|
||||
res = git_futils_readbuffer_updated(
|
||||
&reader->buffer, reader->file_path, &reader->file_mtime, &reader->file_size, &updated);
|
||||
|
||||
if (res < 0)
|
||||
return (res == GIT_ENOTFOUND) ? 0 : res;
|
||||
|
||||
if (updated)
|
||||
any_updated = 1;
|
||||
}
|
||||
|
||||
if (!any_updated)
|
||||
return (res == GIT_ENOTFOUND) ? 0 : res;
|
||||
|
||||
/* need to reload - store old values and prep for reload */
|
||||
@ -196,24 +224,31 @@ static int config_refresh(git_config_backend *cfg)
|
||||
b->values = git_strmap_alloc();
|
||||
GITERR_CHECK_ALLOC(b->values);
|
||||
|
||||
if ((res = config_parse(b, b->level)) < 0) {
|
||||
if ((res = config_parse(b, reader, b->level, 0)) < 0) {
|
||||
free_vars(b->values);
|
||||
b->values = old_values;
|
||||
} else {
|
||||
free_vars(old_values);
|
||||
}
|
||||
|
||||
git_buf_free(&b->reader.buffer);
|
||||
git_buf_free(&reader->buffer);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void backend_free(git_config_backend *_backend)
|
||||
{
|
||||
diskfile_backend *backend = (diskfile_backend *)_backend;
|
||||
uint32_t i;
|
||||
|
||||
if (backend == NULL)
|
||||
return;
|
||||
|
||||
for (i = 0; i < git_array_size(backend->readers); i++) {
|
||||
struct reader *r = git_array_get(backend->readers, i);
|
||||
git__free(r->file_path);
|
||||
}
|
||||
git_array_clear(backend->readers);
|
||||
|
||||
git__free(backend->file_path);
|
||||
free_vars(backend->values);
|
||||
git__free(backend);
|
||||
@ -258,6 +293,8 @@ static int config_iterator_new(
|
||||
diskfile_backend *b = (diskfile_backend *)backend;
|
||||
git_config_file_iter *it = git__calloc(1, sizeof(git_config_file_iter));
|
||||
|
||||
GIT_UNUSED(b);
|
||||
|
||||
GITERR_CHECK_ALLOC(it);
|
||||
|
||||
it->parent.backend = backend;
|
||||
@ -361,6 +398,7 @@ static int config_get(const git_config_backend *cfg, const char *name, const git
|
||||
char *key;
|
||||
khiter_t pos;
|
||||
int error;
|
||||
cvar_t *var;
|
||||
|
||||
if ((error = git_config__normalize_name(name, &key)) < 0)
|
||||
return error;
|
||||
@ -372,7 +410,11 @@ static int config_get(const git_config_backend *cfg, const char *name, const git
|
||||
if (!git_strmap_valid_index(b->values, pos))
|
||||
return GIT_ENOTFOUND;
|
||||
|
||||
*out = ((cvar_t *)git_strmap_value_at(b->values, pos))->entry;
|
||||
var = git_strmap_value_at(b->values, pos);
|
||||
while (var->next)
|
||||
var = var->next;
|
||||
|
||||
*out = var->entry;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -515,26 +557,26 @@ int git_config_file__ondisk(git_config_backend **out, const char *path)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cfg_getchar_raw(diskfile_backend *cfg)
|
||||
static int reader_getchar_raw(struct reader *reader)
|
||||
{
|
||||
int c;
|
||||
|
||||
c = *cfg->reader.read_ptr++;
|
||||
c = *reader->read_ptr++;
|
||||
|
||||
/*
|
||||
Win 32 line breaks: if we find a \r\n sequence,
|
||||
return only the \n as a newline
|
||||
*/
|
||||
if (c == '\r' && *cfg->reader.read_ptr == '\n') {
|
||||
cfg->reader.read_ptr++;
|
||||
if (c == '\r' && *reader->read_ptr == '\n') {
|
||||
reader->read_ptr++;
|
||||
c = '\n';
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
cfg->reader.line_number++;
|
||||
reader->line_number++;
|
||||
|
||||
if (c == 0) {
|
||||
cfg->reader.eof = 1;
|
||||
reader->eof = 1;
|
||||
c = '\n';
|
||||
}
|
||||
|
||||
@ -544,21 +586,23 @@ static int cfg_getchar_raw(diskfile_backend *cfg)
|
||||
#define SKIP_WHITESPACE (1 << 1)
|
||||
#define SKIP_COMMENTS (1 << 2)
|
||||
|
||||
static int cfg_getchar(diskfile_backend *cfg_file, int flags)
|
||||
static int reader_getchar(struct reader *reader, int flags)
|
||||
{
|
||||
const int skip_whitespace = (flags & SKIP_WHITESPACE);
|
||||
const int skip_comments = (flags & SKIP_COMMENTS);
|
||||
int c;
|
||||
|
||||
assert(cfg_file->reader.read_ptr);
|
||||
assert(reader->read_ptr);
|
||||
|
||||
do c = cfg_getchar_raw(cfg_file);
|
||||
while (skip_whitespace && git__isspace(c) &&
|
||||
!cfg_file->reader.eof);
|
||||
do {
|
||||
c = reader_getchar_raw(reader);
|
||||
} while (skip_whitespace && git__isspace(c) &&
|
||||
!reader->eof);
|
||||
|
||||
if (skip_comments && (c == '#' || c == ';')) {
|
||||
do c = cfg_getchar_raw(cfg_file);
|
||||
while (c != '\n');
|
||||
do {
|
||||
c = reader_getchar_raw(reader);
|
||||
} while (c != '\n');
|
||||
}
|
||||
|
||||
return c;
|
||||
@ -567,23 +611,23 @@ static int cfg_getchar(diskfile_backend *cfg_file, int flags)
|
||||
/*
|
||||
* Read the next char, but don't move the reading pointer.
|
||||
*/
|
||||
static int cfg_peek(diskfile_backend *cfg, int flags)
|
||||
static int reader_peek(struct reader *reader, int flags)
|
||||
{
|
||||
void *old_read_ptr;
|
||||
int old_lineno, old_eof;
|
||||
int ret;
|
||||
|
||||
assert(cfg->reader.read_ptr);
|
||||
assert(reader->read_ptr);
|
||||
|
||||
old_read_ptr = cfg->reader.read_ptr;
|
||||
old_lineno = cfg->reader.line_number;
|
||||
old_eof = cfg->reader.eof;
|
||||
old_read_ptr = reader->read_ptr;
|
||||
old_lineno = reader->line_number;
|
||||
old_eof = reader->eof;
|
||||
|
||||
ret = cfg_getchar(cfg, flags);
|
||||
ret = reader_getchar(reader, flags);
|
||||
|
||||
cfg->reader.read_ptr = old_read_ptr;
|
||||
cfg->reader.line_number = old_lineno;
|
||||
cfg->reader.eof = old_eof;
|
||||
reader->read_ptr = old_read_ptr;
|
||||
reader->line_number = old_lineno;
|
||||
reader->eof = old_eof;
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -591,13 +635,13 @@ static int cfg_peek(diskfile_backend *cfg, int flags)
|
||||
/*
|
||||
* Read and consume a line, returning it in newly-allocated memory.
|
||||
*/
|
||||
static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace)
|
||||
static char *reader_readline(struct reader *reader, bool skip_whitespace)
|
||||
{
|
||||
char *line = NULL;
|
||||
char *line_src, *line_end;
|
||||
size_t line_len;
|
||||
|
||||
line_src = cfg->reader.read_ptr;
|
||||
line_src = reader->read_ptr;
|
||||
|
||||
if (skip_whitespace) {
|
||||
/* Skip empty empty lines */
|
||||
@ -626,10 +670,10 @@ static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace)
|
||||
line_end++;
|
||||
|
||||
if (*line_end == '\0')
|
||||
cfg->reader.eof = 1;
|
||||
reader->eof = 1;
|
||||
|
||||
cfg->reader.line_number++;
|
||||
cfg->reader.read_ptr = line_end;
|
||||
reader->line_number++;
|
||||
reader->read_ptr = line_end;
|
||||
|
||||
return line;
|
||||
}
|
||||
@ -637,11 +681,11 @@ static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace)
|
||||
/*
|
||||
* Consume a line, without storing it anywhere
|
||||
*/
|
||||
static void cfg_consume_line(diskfile_backend *cfg)
|
||||
static void reader_consume_line(struct reader *reader)
|
||||
{
|
||||
char *line_start, *line_end;
|
||||
|
||||
line_start = cfg->reader.read_ptr;
|
||||
line_start = reader->read_ptr;
|
||||
line_end = strchr(line_start, '\n');
|
||||
/* No newline at EOF */
|
||||
if(line_end == NULL){
|
||||
@ -652,10 +696,10 @@ static void cfg_consume_line(diskfile_backend *cfg)
|
||||
line_end++;
|
||||
|
||||
if (*line_end == '\0')
|
||||
cfg->reader.eof = 1;
|
||||
reader->eof = 1;
|
||||
|
||||
cfg->reader.line_number++;
|
||||
cfg->reader.read_ptr = line_end;
|
||||
reader->line_number++;
|
||||
reader->read_ptr = line_end;
|
||||
}
|
||||
|
||||
GIT_INLINE(int) config_keychar(int c)
|
||||
@ -663,7 +707,7 @@ GIT_INLINE(int) config_keychar(int c)
|
||||
return isalnum(c) || c == '-';
|
||||
}
|
||||
|
||||
static int parse_section_header_ext(diskfile_backend *cfg, const char *line, const char *base_name, char **section_name)
|
||||
static int parse_section_header_ext(struct reader *reader, const char *line, const char *base_name, char **section_name)
|
||||
{
|
||||
int c, rpos;
|
||||
char *first_quote, *last_quote;
|
||||
@ -679,7 +723,7 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
|
||||
last_quote = strrchr(line, '"');
|
||||
|
||||
if (last_quote - first_quote == 0) {
|
||||
set_parse_error(cfg, 0, "Missing closing quotation mark in section header");
|
||||
set_parse_error(reader, 0, "Missing closing quotation mark in section header");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -698,14 +742,14 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
|
||||
*/
|
||||
do {
|
||||
if (quote_marks == 2) {
|
||||
set_parse_error(cfg, rpos, "Unexpected text after closing quotes");
|
||||
set_parse_error(reader, rpos, "Unexpected text after closing quotes");
|
||||
git_buf_free(&buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case 0:
|
||||
set_parse_error(cfg, 0, "Unexpected end-of-line in section header");
|
||||
set_parse_error(reader, 0, "Unexpected end-of-line in section header");
|
||||
git_buf_free(&buf);
|
||||
return -1;
|
||||
|
||||
@ -719,7 +763,7 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
|
||||
switch (c) {
|
||||
case '"':
|
||||
if (&line[rpos-1] == last_quote) {
|
||||
set_parse_error(cfg, 0, "Missing closing quotation mark in section header");
|
||||
set_parse_error(reader, 0, "Missing closing quotation mark in section header");
|
||||
git_buf_free(&buf);
|
||||
return -1;
|
||||
}
|
||||
@ -728,7 +772,7 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
|
||||
break;
|
||||
|
||||
default:
|
||||
set_parse_error(cfg, rpos, "Unsupported escape sequence");
|
||||
set_parse_error(reader, rpos, "Unsupported escape sequence");
|
||||
git_buf_free(&buf);
|
||||
return -1;
|
||||
}
|
||||
@ -737,21 +781,21 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
|
||||
break;
|
||||
}
|
||||
|
||||
git_buf_putc(&buf, c);
|
||||
git_buf_putc(&buf, (char)c);
|
||||
} while ((c = line[rpos++]) != ']');
|
||||
|
||||
*section_name = git_buf_detach(&buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_section_header(diskfile_backend *cfg, char **section_out)
|
||||
static int parse_section_header(struct reader *reader, char **section_out)
|
||||
{
|
||||
char *name, *name_end;
|
||||
int name_length, c, pos;
|
||||
int result;
|
||||
char *line;
|
||||
|
||||
line = cfg_readline(cfg, true);
|
||||
line = reader_readline(reader, true);
|
||||
if (line == NULL)
|
||||
return -1;
|
||||
|
||||
@ -759,7 +803,7 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out)
|
||||
name_end = strchr(line, ']');
|
||||
if (name_end == NULL) {
|
||||
git__free(line);
|
||||
set_parse_error(cfg, 0, "Missing ']' in section header");
|
||||
set_parse_error(reader, 0, "Missing ']' in section header");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -778,14 +822,14 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out)
|
||||
do {
|
||||
if (git__isspace(c)){
|
||||
name[name_length] = '\0';
|
||||
result = parse_section_header_ext(cfg, line, name, section_out);
|
||||
result = parse_section_header_ext(reader, line, name, section_out);
|
||||
git__free(line);
|
||||
git__free(name);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!config_keychar(c) && c != '.') {
|
||||
set_parse_error(cfg, pos, "Unexpected character in header");
|
||||
set_parse_error(reader, pos, "Unexpected character in header");
|
||||
goto fail_parse;
|
||||
}
|
||||
|
||||
@ -794,7 +838,7 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out)
|
||||
} while ((c = line[pos++]) != ']');
|
||||
|
||||
if (line[pos - 1] != ']') {
|
||||
set_parse_error(cfg, pos, "Unexpected end of file");
|
||||
set_parse_error(reader, pos, "Unexpected end of file");
|
||||
goto fail_parse;
|
||||
}
|
||||
|
||||
@ -811,14 +855,14 @@ fail_parse:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int skip_bom(diskfile_backend *cfg)
|
||||
static int skip_bom(struct reader *reader)
|
||||
{
|
||||
git_bom_t bom;
|
||||
int bom_offset = git_buf_text_detect_bom(&bom,
|
||||
&cfg->reader.buffer, cfg->reader.read_ptr - cfg->reader.buffer.ptr);
|
||||
&reader->buffer, reader->read_ptr - reader->buffer.ptr);
|
||||
|
||||
if (bom == GIT_BOM_UTF8)
|
||||
cfg->reader.read_ptr += bom_offset;
|
||||
reader->read_ptr += bom_offset;
|
||||
|
||||
/* TODO: reference implementation is pretty stupid with BoM */
|
||||
|
||||
@ -888,7 +932,16 @@ static int strip_comments(char *line, int in_quotes)
|
||||
return quote_count;
|
||||
}
|
||||
|
||||
static int config_parse(diskfile_backend *cfg_file, git_config_level_t level)
|
||||
static int included_path(git_buf *out, const char *dir, const char *path)
|
||||
{
|
||||
/* From the user's home */
|
||||
if (path[0] == '~' && path[1] == '/')
|
||||
return git_futils_find_global_file(out, &path[1]);
|
||||
|
||||
return git_path_join_unrooted(out, path, dir, NULL);
|
||||
}
|
||||
|
||||
static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth)
|
||||
{
|
||||
int c;
|
||||
char *current_section = NULL;
|
||||
@ -898,39 +951,46 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level)
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
int result = 0;
|
||||
khiter_t pos;
|
||||
uint32_t reader_idx;
|
||||
|
||||
if (depth >= MAX_INCLUDE_DEPTH) {
|
||||
giterr_set(GITERR_CONFIG, "Maximum config include depth reached");
|
||||
return -1;
|
||||
}
|
||||
|
||||
reader_idx = git_array_size(cfg_file->readers) - 1;
|
||||
/* Initialize the reading position */
|
||||
cfg_file->reader.read_ptr = cfg_file->reader.buffer.ptr;
|
||||
cfg_file->reader.eof = 0;
|
||||
reader->read_ptr = reader->buffer.ptr;
|
||||
reader->eof = 0;
|
||||
|
||||
/* If the file is empty, there's nothing for us to do */
|
||||
if (*cfg_file->reader.read_ptr == '\0')
|
||||
if (*reader->read_ptr == '\0')
|
||||
return 0;
|
||||
|
||||
skip_bom(cfg_file);
|
||||
skip_bom(reader);
|
||||
|
||||
while (result == 0 && !cfg_file->reader.eof) {
|
||||
while (result == 0 && !reader->eof) {
|
||||
|
||||
c = cfg_peek(cfg_file, SKIP_WHITESPACE);
|
||||
c = reader_peek(reader, SKIP_WHITESPACE);
|
||||
|
||||
switch (c) {
|
||||
case '\n': /* EOF when peeking, set EOF in the reader to exit the loop */
|
||||
cfg_file->reader.eof = 1;
|
||||
reader->eof = 1;
|
||||
break;
|
||||
|
||||
case '[': /* section header, new section begins */
|
||||
git__free(current_section);
|
||||
current_section = NULL;
|
||||
result = parse_section_header(cfg_file, ¤t_section);
|
||||
result = parse_section_header(reader, ¤t_section);
|
||||
break;
|
||||
|
||||
case ';':
|
||||
case '#':
|
||||
cfg_consume_line(cfg_file);
|
||||
reader_consume_line(reader);
|
||||
break;
|
||||
|
||||
default: /* assume variable declaration */
|
||||
result = parse_variable(cfg_file, &var_name, &var_value);
|
||||
result = parse_variable(reader, &var_name, &var_value);
|
||||
if (result < 0)
|
||||
break;
|
||||
|
||||
@ -951,6 +1011,7 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level)
|
||||
var->entry->name = git_buf_detach(&buf);
|
||||
var->entry->value = var_value;
|
||||
var->entry->level = level;
|
||||
var->included = !!depth;
|
||||
|
||||
/* Add or append the new config option */
|
||||
pos = git_strmap_lookup_index(cfg_file->values, var->entry->name);
|
||||
@ -967,6 +1028,42 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level)
|
||||
existing->next = var;
|
||||
}
|
||||
|
||||
if (!git__strcmp(var->entry->name, "include.path")) {
|
||||
struct reader *r;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
char *dir;
|
||||
uint32_t index;
|
||||
|
||||
r = git_array_alloc(cfg_file->readers);
|
||||
/* The reader may have been reallocated */
|
||||
reader = git_array_get(cfg_file->readers, reader_idx);
|
||||
memset(r, 0, sizeof(struct reader));
|
||||
if ((result = git_path_dirname_r(&path, reader->file_path)) < 0)
|
||||
break;
|
||||
|
||||
/* We need to know out index in the array, as the next config_parse call may realloc */
|
||||
index = git_array_size(cfg_file->readers) - 1;
|
||||
dir = git_buf_detach(&path);
|
||||
result = included_path(&path, dir, var->entry->value);
|
||||
git__free(dir);
|
||||
|
||||
if (result < 0)
|
||||
break;
|
||||
|
||||
r->file_path = git_buf_detach(&path);
|
||||
git_buf_init(&r->buffer, 0);
|
||||
if ((result = git_futils_readbuffer_updated(&r->buffer, r->file_path, &r->file_mtime,
|
||||
&r->file_size, NULL)) < 0)
|
||||
break;
|
||||
|
||||
result = config_parse(cfg_file, r, level, depth+1);
|
||||
r = git_array_get(cfg_file->readers, index);
|
||||
git_buf_free(&r->buffer);
|
||||
|
||||
if (result < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1015,20 +1112,21 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
|
||||
const char *pre_end = NULL, *post_start = NULL, *data_start;
|
||||
char *current_section = NULL, *section, *name, *ldot;
|
||||
git_filebuf file = GIT_FILEBUF_INIT;
|
||||
struct reader *reader = git_array_get(cfg->readers, 0);
|
||||
|
||||
/* We need to read in our own config file */
|
||||
result = git_futils_readbuffer(&cfg->reader.buffer, cfg->file_path);
|
||||
result = git_futils_readbuffer(&reader->buffer, cfg->file_path);
|
||||
|
||||
/* Initialise the reading position */
|
||||
if (result == GIT_ENOTFOUND) {
|
||||
cfg->reader.read_ptr = NULL;
|
||||
cfg->reader.eof = 1;
|
||||
reader->read_ptr = NULL;
|
||||
reader->eof = 1;
|
||||
data_start = NULL;
|
||||
git_buf_clear(&cfg->reader.buffer);
|
||||
git_buf_clear(&reader->buffer);
|
||||
} else if (result == 0) {
|
||||
cfg->reader.read_ptr = cfg->reader.buffer.ptr;
|
||||
cfg->reader.eof = 0;
|
||||
data_start = cfg->reader.read_ptr;
|
||||
reader->read_ptr = reader->buffer.ptr;
|
||||
reader->eof = 0;
|
||||
data_start = reader->read_ptr;
|
||||
} else {
|
||||
return -1; /* OS error when reading the file */
|
||||
}
|
||||
@ -1037,13 +1135,13 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
|
||||
if (git_filebuf_open(&file, cfg->file_path, 0) < 0)
|
||||
return -1;
|
||||
|
||||
skip_bom(cfg);
|
||||
skip_bom(reader);
|
||||
ldot = strrchr(key, '.');
|
||||
name = ldot + 1;
|
||||
section = git__strndup(key, ldot - key);
|
||||
|
||||
while (!cfg->reader.eof) {
|
||||
c = cfg_peek(cfg, SKIP_WHITESPACE);
|
||||
while (!reader->eof) {
|
||||
c = reader_peek(reader, SKIP_WHITESPACE);
|
||||
|
||||
if (c == '\0') { /* We've arrived at the end of the file */
|
||||
break;
|
||||
@ -1056,11 +1154,11 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
|
||||
* new section. If we actually want to replace it, the
|
||||
* default case will take care of updating them.
|
||||
*/
|
||||
pre_end = post_start = cfg->reader.read_ptr;
|
||||
pre_end = post_start = reader->read_ptr;
|
||||
|
||||
git__free(current_section);
|
||||
current_section = NULL;
|
||||
if (parse_section_header(cfg, ¤t_section) < 0)
|
||||
if (parse_section_header(reader, ¤t_section) < 0)
|
||||
goto rewrite_fail;
|
||||
|
||||
/* Keep track of when it stops matching */
|
||||
@ -1069,7 +1167,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
|
||||
}
|
||||
|
||||
else if (c == ';' || c == '#') {
|
||||
cfg_consume_line(cfg);
|
||||
reader_consume_line(reader);
|
||||
}
|
||||
|
||||
else {
|
||||
@ -1085,15 +1183,15 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
|
||||
*/
|
||||
if (!section_matches) {
|
||||
if (!last_section_matched) {
|
||||
cfg_consume_line(cfg);
|
||||
reader_consume_line(reader);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
int has_matched = 0;
|
||||
char *var_name, *var_value;
|
||||
|
||||
pre_end = cfg->reader.read_ptr;
|
||||
if (parse_variable(cfg, &var_name, &var_value) < 0)
|
||||
pre_end = reader->read_ptr;
|
||||
if (parse_variable(reader, &var_name, &var_value) < 0)
|
||||
goto rewrite_fail;
|
||||
|
||||
/* First try to match the name of the variable */
|
||||
@ -1112,7 +1210,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
|
||||
if (!has_matched)
|
||||
continue;
|
||||
|
||||
post_start = cfg->reader.read_ptr;
|
||||
post_start = reader->read_ptr;
|
||||
}
|
||||
|
||||
/* We've found the variable we wanted to change, so
|
||||
@ -1155,12 +1253,12 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
|
||||
*/
|
||||
if (write_trailer) {
|
||||
/* Write out rest of the file */
|
||||
git_filebuf_write(&file, post_start, cfg->reader.buffer.size - (post_start - data_start));
|
||||
git_filebuf_write(&file, post_start, reader->buffer.size - (post_start - data_start));
|
||||
} else {
|
||||
if (preg_replaced) {
|
||||
git_filebuf_printf(&file, "\n%s", data_start);
|
||||
} else {
|
||||
git_filebuf_write(&file, cfg->reader.buffer.ptr, cfg->reader.buffer.size);
|
||||
git_filebuf_write(&file, reader->buffer.ptr, reader->buffer.size);
|
||||
|
||||
/* And now if we just need to add a variable */
|
||||
if (!section_matches && write_section(&file, section) < 0)
|
||||
@ -1176,7 +1274,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
|
||||
}
|
||||
|
||||
/* If we are here, there is at least a section line */
|
||||
if (cfg->reader.buffer.size > 0 && *(cfg->reader.buffer.ptr + cfg->reader.buffer.size - 1) != '\n')
|
||||
if (reader->buffer.size > 0 && *(reader->buffer.ptr + reader->buffer.size - 1) != '\n')
|
||||
git_filebuf_write(&file, "\n", 1);
|
||||
|
||||
git_filebuf_printf(&file, "\t%s = %s\n", name, value);
|
||||
@ -1187,10 +1285,10 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
|
||||
git__free(current_section);
|
||||
|
||||
/* refresh stats - if this errors, then commit will error too */
|
||||
(void)git_filebuf_stats(&cfg->file_mtime, &cfg->file_size, &file);
|
||||
(void)git_filebuf_stats(&reader->file_mtime, &reader->file_size, &file);
|
||||
|
||||
result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE);
|
||||
git_buf_free(&cfg->reader.buffer);
|
||||
git_buf_free(&reader->buffer);
|
||||
|
||||
return result;
|
||||
|
||||
@ -1199,7 +1297,7 @@ rewrite_fail:
|
||||
git__free(current_section);
|
||||
|
||||
git_filebuf_cleanup(&file);
|
||||
git_buf_free(&cfg->reader.buffer);
|
||||
git_buf_free(&reader->buffer);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -1291,19 +1389,19 @@ static int is_multiline_var(const char *str)
|
||||
return (end > str) && (count & 1);
|
||||
}
|
||||
|
||||
static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int in_quotes)
|
||||
static int parse_multiline_variable(struct reader *reader, git_buf *value, int in_quotes)
|
||||
{
|
||||
char *line = NULL, *proc_line = NULL;
|
||||
int quote_count;
|
||||
|
||||
/* Check that the next line exists */
|
||||
line = cfg_readline(cfg, false);
|
||||
line = reader_readline(reader, false);
|
||||
if (line == NULL)
|
||||
return -1;
|
||||
|
||||
/* We've reached the end of the file, there is input missing */
|
||||
if (line[0] == '\0') {
|
||||
set_parse_error(cfg, 0, "Unexpected end of file while parsing multine var");
|
||||
set_parse_error(reader, 0, "Unexpected end of file while parsing multine var");
|
||||
git__free(line);
|
||||
return -1;
|
||||
}
|
||||
@ -1313,7 +1411,7 @@ static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int i
|
||||
/* If it was just a comment, pretend it didn't exist */
|
||||
if (line[0] == '\0') {
|
||||
git__free(line);
|
||||
return parse_multiline_variable(cfg, value, quote_count);
|
||||
return parse_multiline_variable(reader, value, quote_count);
|
||||
/* TODO: unbounded recursion. This **could** be exploitable */
|
||||
}
|
||||
|
||||
@ -1338,19 +1436,19 @@ static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int i
|
||||
* keep putting stuff in the buffer
|
||||
*/
|
||||
if (is_multiline_var(value->ptr))
|
||||
return parse_multiline_variable(cfg, value, quote_count);
|
||||
return parse_multiline_variable(reader, value, quote_count);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value)
|
||||
static int parse_variable(struct reader *reader, char **var_name, char **var_value)
|
||||
{
|
||||
const char *var_end = NULL;
|
||||
const char *value_start = NULL;
|
||||
char *line;
|
||||
int quote_count;
|
||||
|
||||
line = cfg_readline(cfg, true);
|
||||
line = reader_readline(reader, true);
|
||||
if (line == NULL)
|
||||
return -1;
|
||||
|
||||
@ -1385,7 +1483,7 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val
|
||||
GITERR_CHECK_ALLOC(proc_line);
|
||||
git_buf_puts(&multi_value, proc_line);
|
||||
git__free(proc_line);
|
||||
if (parse_multiline_variable(cfg, &multi_value, quote_count) < 0 || git_buf_oom(&multi_value)) {
|
||||
if (parse_multiline_variable(reader, &multi_value, quote_count) < 0 || git_buf_oom(&multi_value)) {
|
||||
git__free(*var_name);
|
||||
git__free(line);
|
||||
git_buf_free(&multi_value);
|
||||
|
||||
232
src/crlf.c
232
src/crlf.c
@ -8,6 +8,7 @@
|
||||
#include "git2/attr.h"
|
||||
#include "git2/blob.h"
|
||||
#include "git2/index.h"
|
||||
#include "git2/sys/filter.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "fileops.h"
|
||||
@ -19,13 +20,11 @@
|
||||
struct crlf_attrs {
|
||||
int crlf_action;
|
||||
int eol;
|
||||
int auto_crlf;
|
||||
};
|
||||
|
||||
struct crlf_filter {
|
||||
git_filter f;
|
||||
struct crlf_attrs attrs;
|
||||
git_repository *repo;
|
||||
char path[GIT_FLEX_ARRAY];
|
||||
};
|
||||
|
||||
static int check_crlf(const char *value)
|
||||
@ -76,41 +75,10 @@ static int crlf_input_action(struct crlf_attrs *ca)
|
||||
return ca->crlf_action;
|
||||
}
|
||||
|
||||
static int crlf_load_attributes(struct crlf_attrs *ca, git_repository *repo, const char *path)
|
||||
static int has_cr_in_index(const git_filter_source *src)
|
||||
{
|
||||
#define NUM_CONV_ATTRS 3
|
||||
|
||||
static const char *attr_names[NUM_CONV_ATTRS] = {
|
||||
"crlf", "eol", "text",
|
||||
};
|
||||
|
||||
const char *attr_vals[NUM_CONV_ATTRS];
|
||||
int error;
|
||||
|
||||
error = git_attr_get_many(attr_vals,
|
||||
repo, 0, path, NUM_CONV_ATTRS, attr_names);
|
||||
|
||||
if (error == GIT_ENOTFOUND) {
|
||||
ca->crlf_action = GIT_CRLF_GUESS;
|
||||
ca->eol = GIT_EOL_UNSET;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (error == 0) {
|
||||
ca->crlf_action = check_crlf(attr_vals[2]); /* text */
|
||||
if (ca->crlf_action == GIT_CRLF_GUESS)
|
||||
ca->crlf_action = check_crlf(attr_vals[0]); /* clrf */
|
||||
|
||||
ca->eol = check_eol(attr_vals[1]); /* eol */
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int has_cr_in_index(git_filter *self)
|
||||
{
|
||||
struct crlf_filter *filter = (struct crlf_filter *)self;
|
||||
git_repository *repo = git_filter_source_repo(src);
|
||||
const char *path = git_filter_source_path(src);
|
||||
git_index *index;
|
||||
const git_index_entry *entry;
|
||||
git_blob *blob;
|
||||
@ -118,19 +86,22 @@ static int has_cr_in_index(git_filter *self)
|
||||
git_off_t blobsize;
|
||||
bool found_cr;
|
||||
|
||||
if (git_repository_index__weakptr(&index, filter->repo) < 0) {
|
||||
if (!path)
|
||||
return false;
|
||||
|
||||
if (git_repository_index__weakptr(&index, repo) < 0) {
|
||||
giterr_clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(entry = git_index_get_bypath(index, filter->path, 0)) &&
|
||||
!(entry = git_index_get_bypath(index, filter->path, 1)))
|
||||
if (!(entry = git_index_get_bypath(index, path, 0)) &&
|
||||
!(entry = git_index_get_bypath(index, path, 1)))
|
||||
return false;
|
||||
|
||||
if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */
|
||||
return true;
|
||||
|
||||
if (git_blob_lookup(&blob, filter->repo, &entry->oid) < 0)
|
||||
if (git_blob_lookup(&blob, repo, &entry->oid) < 0)
|
||||
return false;
|
||||
|
||||
blobcontent = git_blob_rawcontent(blob);
|
||||
@ -147,27 +118,24 @@ static int has_cr_in_index(git_filter *self)
|
||||
}
|
||||
|
||||
static int crlf_apply_to_odb(
|
||||
git_filter *self, git_buf *dest, const git_buf *source)
|
||||
struct crlf_attrs *ca,
|
||||
git_buf *to,
|
||||
const git_buf *from,
|
||||
const git_filter_source *src)
|
||||
{
|
||||
struct crlf_filter *filter = (struct crlf_filter *)self;
|
||||
|
||||
assert(self && dest && source);
|
||||
|
||||
/* Empty file? Nothing to do */
|
||||
if (git_buf_len(source) == 0)
|
||||
if (!git_buf_len(from))
|
||||
return 0;
|
||||
|
||||
/* Heuristics to see if we can skip the conversion.
|
||||
* Straight from Core Git.
|
||||
*/
|
||||
if (filter->attrs.crlf_action == GIT_CRLF_AUTO ||
|
||||
filter->attrs.crlf_action == GIT_CRLF_GUESS) {
|
||||
|
||||
if (ca->crlf_action == GIT_CRLF_AUTO || ca->crlf_action == GIT_CRLF_GUESS) {
|
||||
git_buf_text_stats stats;
|
||||
|
||||
/* Check heuristics for binary vs text... */
|
||||
if (git_buf_text_gather_stats(&stats, source, false))
|
||||
return -1;
|
||||
/* Check heuristics for binary vs text - returns true if binary */
|
||||
if (git_buf_text_gather_stats(&stats, from, false))
|
||||
return GIT_PASSTHROUGH;
|
||||
|
||||
/*
|
||||
* We're currently not going to even try to convert stuff
|
||||
@ -175,28 +143,28 @@ static int crlf_apply_to_odb(
|
||||
* stuff?
|
||||
*/
|
||||
if (stats.cr != stats.crlf)
|
||||
return -1;
|
||||
return GIT_PASSTHROUGH;
|
||||
|
||||
if (filter->attrs.crlf_action == GIT_CRLF_GUESS) {
|
||||
if (ca->crlf_action == GIT_CRLF_GUESS) {
|
||||
/*
|
||||
* If the file in the index has any CR in it, do not convert.
|
||||
* This is the new safer autocrlf handling.
|
||||
*/
|
||||
if (has_cr_in_index(self))
|
||||
return -1;
|
||||
if (has_cr_in_index(src))
|
||||
return GIT_PASSTHROUGH;
|
||||
}
|
||||
|
||||
if (!stats.cr)
|
||||
return -1;
|
||||
return GIT_PASSTHROUGH;
|
||||
}
|
||||
|
||||
/* Actually drop the carriage returns */
|
||||
return git_buf_text_crlf_to_lf(dest, source);
|
||||
return git_buf_text_crlf_to_lf(to, from);
|
||||
}
|
||||
|
||||
static const char *line_ending(struct crlf_filter *filter)
|
||||
static const char *line_ending(struct crlf_attrs *ca)
|
||||
{
|
||||
switch (filter->attrs.crlf_action) {
|
||||
switch (ca->crlf_action) {
|
||||
case GIT_CRLF_BINARY:
|
||||
case GIT_CRLF_INPUT:
|
||||
return "\n";
|
||||
@ -213,11 +181,9 @@ static const char *line_ending(struct crlf_filter *filter)
|
||||
goto line_ending_error;
|
||||
}
|
||||
|
||||
switch (filter->attrs.eol) {
|
||||
switch (ca->eol) {
|
||||
case GIT_EOL_UNSET:
|
||||
return GIT_EOL_NATIVE == GIT_EOL_CRLF
|
||||
? "\r\n"
|
||||
: "\n";
|
||||
return GIT_EOL_NATIVE == GIT_EOL_CRLF ? "\r\n" : "\n";
|
||||
|
||||
case GIT_EOL_CRLF:
|
||||
return "\r\n";
|
||||
@ -235,41 +201,64 @@ line_ending_error:
|
||||
}
|
||||
|
||||
static int crlf_apply_to_workdir(
|
||||
git_filter *self, git_buf *dest, const git_buf *source)
|
||||
struct crlf_attrs *ca, git_buf *to, const git_buf *from)
|
||||
{
|
||||
struct crlf_filter *filter = (struct crlf_filter *)self;
|
||||
const char *workdir_ending = NULL;
|
||||
|
||||
assert(self && dest && source);
|
||||
|
||||
/* Empty file? Nothing to do. */
|
||||
if (git_buf_len(source) == 0)
|
||||
return -1;
|
||||
if (git_buf_len(from) == 0)
|
||||
return 0;
|
||||
|
||||
/* Don't filter binary files */
|
||||
if (git_buf_text_is_binary(from))
|
||||
return GIT_PASSTHROUGH;
|
||||
|
||||
/* Determine proper line ending */
|
||||
workdir_ending = line_ending(filter);
|
||||
workdir_ending = line_ending(ca);
|
||||
if (!workdir_ending)
|
||||
return -1;
|
||||
if (!strcmp("\n", workdir_ending)) /* do nothing for \n ending */
|
||||
return -1;
|
||||
|
||||
/* for now, only lf->crlf conversion is supported here */
|
||||
assert(!strcmp("\r\n", workdir_ending));
|
||||
return git_buf_text_lf_to_crlf(dest, source);
|
||||
if (!strcmp("\n", workdir_ending)) {
|
||||
if (ca->crlf_action == GIT_CRLF_GUESS && ca->auto_crlf)
|
||||
return GIT_PASSTHROUGH;
|
||||
|
||||
if (git_buf_find(from, '\r') < 0)
|
||||
return GIT_PASSTHROUGH;
|
||||
|
||||
if (git_buf_text_crlf_to_lf(to, from) < 0)
|
||||
return -1;
|
||||
} else {
|
||||
/* only other supported option is lf->crlf conversion */
|
||||
assert(!strcmp("\r\n", workdir_ending));
|
||||
|
||||
if (git_buf_text_lf_to_crlf(to, from) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int find_and_add_filter(
|
||||
git_vector *filters, git_repository *repo, const char *path,
|
||||
int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source))
|
||||
static int crlf_check(
|
||||
git_filter *self,
|
||||
void **payload, /* points to NULL ptr on entry, may be set */
|
||||
const git_filter_source *src,
|
||||
const char **attr_values)
|
||||
{
|
||||
struct crlf_attrs ca;
|
||||
struct crlf_filter *filter;
|
||||
size_t pathlen;
|
||||
int error;
|
||||
struct crlf_attrs ca;
|
||||
|
||||
/* Load gitattributes for the path */
|
||||
if ((error = crlf_load_attributes(&ca, repo, path)) < 0)
|
||||
return error;
|
||||
GIT_UNUSED(self);
|
||||
|
||||
if (!attr_values) {
|
||||
ca.crlf_action = GIT_CRLF_GUESS;
|
||||
ca.eol = GIT_EOL_UNSET;
|
||||
} else {
|
||||
ca.crlf_action = check_crlf(attr_values[2]); /* text */
|
||||
if (ca.crlf_action == GIT_CRLF_GUESS)
|
||||
ca.crlf_action = check_crlf(attr_values[0]); /* clrf */
|
||||
ca.eol = check_eol(attr_values[1]); /* eol */
|
||||
}
|
||||
ca.auto_crlf = GIT_AUTO_CRLF_DEFAULT;
|
||||
|
||||
/*
|
||||
* Use the core Git logic to see if we should perform CRLF for this file
|
||||
@ -278,41 +267,64 @@ static int find_and_add_filter(
|
||||
ca.crlf_action = crlf_input_action(&ca);
|
||||
|
||||
if (ca.crlf_action == GIT_CRLF_BINARY)
|
||||
return 0;
|
||||
return GIT_PASSTHROUGH;
|
||||
|
||||
if (ca.crlf_action == GIT_CRLF_GUESS) {
|
||||
int auto_crlf;
|
||||
|
||||
if ((error = git_repository__cvar(&auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0)
|
||||
error = git_repository__cvar(
|
||||
&ca.auto_crlf, git_filter_source_repo(src), GIT_CVAR_AUTO_CRLF);
|
||||
if (error < 0)
|
||||
return error;
|
||||
|
||||
if (auto_crlf == GIT_AUTO_CRLF_FALSE)
|
||||
return 0;
|
||||
if (ca.auto_crlf == GIT_AUTO_CRLF_FALSE)
|
||||
return GIT_PASSTHROUGH;
|
||||
}
|
||||
|
||||
/* If we're good, we create a new filter object and push it
|
||||
* into the filters array */
|
||||
pathlen = strlen(path);
|
||||
filter = git__malloc(sizeof(struct crlf_filter) + pathlen + 1);
|
||||
GITERR_CHECK_ALLOC(filter);
|
||||
*payload = git__malloc(sizeof(ca));
|
||||
GITERR_CHECK_ALLOC(*payload);
|
||||
memcpy(*payload, &ca, sizeof(ca));
|
||||
|
||||
filter->f.apply = apply;
|
||||
filter->f.do_free = NULL;
|
||||
memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs));
|
||||
filter->repo = repo;
|
||||
memcpy(filter->path, path, pathlen + 1);
|
||||
|
||||
return git_vector_insert(filters, filter);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_filter_add__crlf_to_odb(
|
||||
git_vector *filters, git_repository *repo, const char *path)
|
||||
static int crlf_apply(
|
||||
git_filter *self,
|
||||
void **payload, /* may be read and/or set */
|
||||
git_buf *to,
|
||||
const git_buf *from,
|
||||
const git_filter_source *src)
|
||||
{
|
||||
return find_and_add_filter(filters, repo, path, &crlf_apply_to_odb);
|
||||
/* initialize payload in case `check` was bypassed */
|
||||
if (!*payload) {
|
||||
int error = crlf_check(self, payload, src, NULL);
|
||||
if (error < 0 && error != GIT_PASSTHROUGH)
|
||||
return error;
|
||||
}
|
||||
|
||||
if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
|
||||
return crlf_apply_to_workdir(*payload, to, from);
|
||||
else
|
||||
return crlf_apply_to_odb(*payload, to, from, src);
|
||||
}
|
||||
|
||||
int git_filter_add__crlf_to_workdir(
|
||||
git_vector *filters, git_repository *repo, const char *path)
|
||||
static void crlf_cleanup(
|
||||
git_filter *self,
|
||||
void *payload)
|
||||
{
|
||||
return find_and_add_filter(filters, repo, path, &crlf_apply_to_workdir);
|
||||
GIT_UNUSED(self);
|
||||
git__free(payload);
|
||||
}
|
||||
|
||||
git_filter *git_crlf_filter_new(void)
|
||||
{
|
||||
struct crlf_filter *f = git__calloc(1, sizeof(struct crlf_filter));
|
||||
|
||||
f->f.version = GIT_FILTER_VERSION;
|
||||
f->f.attributes = "crlf eol text";
|
||||
f->f.initialize = NULL;
|
||||
f->f.shutdown = git_filter_free;
|
||||
f->f.check = crlf_check;
|
||||
f->f.apply = crlf_apply;
|
||||
f->f.cleanup = crlf_cleanup;
|
||||
|
||||
return (git_filter *)f;
|
||||
}
|
||||
|
||||
15
src/diff.c
15
src/diff.c
@ -440,7 +440,8 @@ static int diff_list_apply_options(
|
||||
|
||||
/* If not given explicit `opts`, check `diff.xyz` configs */
|
||||
if (!opts) {
|
||||
diff->opts.context_lines = config_int(cfg, "diff.context", 3);
|
||||
int context = config_int(cfg, "diff.context", 3);
|
||||
diff->opts.context_lines = context >= 0 ? (uint16_t)context : 3;
|
||||
|
||||
/* add other defaults here */
|
||||
}
|
||||
@ -568,21 +569,21 @@ int git_diff__oid_for_file(
|
||||
giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'", path);
|
||||
result = -1;
|
||||
} else {
|
||||
git_vector filters = GIT_VECTOR_INIT;
|
||||
git_filter_list *fl = NULL;
|
||||
|
||||
result = git_filters_load(&filters, repo, path, GIT_FILTER_TO_ODB);
|
||||
if (result >= 0) {
|
||||
result = git_filter_list_load(&fl, repo, NULL, path, GIT_FILTER_TO_ODB);
|
||||
if (!result) {
|
||||
int fd = git_futils_open_ro(full_path.ptr);
|
||||
if (fd < 0)
|
||||
result = fd;
|
||||
else {
|
||||
result = git_odb__hashfd_filtered(
|
||||
oid, fd, (size_t)size, GIT_OBJ_BLOB, &filters);
|
||||
oid, fd, (size_t)size, GIT_OBJ_BLOB, fl);
|
||||
p_close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
git_filters_free(&filters);
|
||||
git_filter_list_free(fl);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
|
||||
@ -296,9 +296,9 @@ static int diff_file_content_load_workdir_file(
|
||||
git_diff_file_content *fc, git_buf *path)
|
||||
{
|
||||
int error = 0;
|
||||
git_vector filters = GIT_VECTOR_INIT;
|
||||
git_buf raw = GIT_BUF_INIT, filtered = GIT_BUF_INIT;
|
||||
git_filter_list *fl = NULL;
|
||||
git_file fd = git_futils_open_ro(git_buf_cstr(path));
|
||||
git_buf raw = GIT_BUF_INIT;
|
||||
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
@ -310,41 +310,38 @@ static int diff_file_content_load_workdir_file(
|
||||
if (diff_file_content_binary_by_size(fc))
|
||||
goto cleanup;
|
||||
|
||||
if ((error = git_filters_load(
|
||||
&filters, fc->repo, fc->file->path, GIT_FILTER_TO_ODB)) < 0)
|
||||
if ((error = git_filter_list_load(
|
||||
&fl, fc->repo, NULL, fc->file->path, GIT_FILTER_TO_ODB)) < 0)
|
||||
goto cleanup;
|
||||
/* error >= is a filter count */
|
||||
|
||||
if (error == 0) {
|
||||
/* if there are no filters, try to mmap the file */
|
||||
if (fl == NULL) {
|
||||
if (!(error = git_futils_mmap_ro(
|
||||
&fc->map, fd, 0, (size_t)fc->file->size)))
|
||||
&fc->map, fd, 0, (size_t)fc->file->size))) {
|
||||
fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA;
|
||||
else /* fall through to try readbuffer below */
|
||||
giterr_clear();
|
||||
}
|
||||
|
||||
if (error != 0) {
|
||||
error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (!filters.length)
|
||||
git_buf_swap(&filtered, &raw);
|
||||
else
|
||||
error = git_filters_apply(&filtered, &raw, &filters);
|
||||
|
||||
if (!error) {
|
||||
fc->map.len = git_buf_len(&filtered);
|
||||
fc->map.data = git_buf_detach(&filtered);
|
||||
fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
|
||||
}
|
||||
|
||||
/* if mmap failed, fall through to try readbuffer below */
|
||||
giterr_clear();
|
||||
}
|
||||
|
||||
if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size))) {
|
||||
git_buf out = GIT_BUF_INIT;
|
||||
|
||||
error = git_filter_list_apply_to_data(&out, fl, &raw);
|
||||
|
||||
git_buf_free(&raw);
|
||||
git_buf_free(&filtered);
|
||||
|
||||
if (!error) {
|
||||
fc->map.len = out.size;
|
||||
fc->map.data = out.ptr;
|
||||
fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
git_filters_free(&filters);
|
||||
git_filter_list_free(fl);
|
||||
p_close(fd);
|
||||
|
||||
return error;
|
||||
|
||||
@ -336,7 +336,7 @@ static int diff_print_patch_hunk(
|
||||
return 0;
|
||||
|
||||
git_buf_clear(pi->buf);
|
||||
if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) < 0)
|
||||
if (git_buf_put(pi->buf, header, header_len) < 0)
|
||||
return -1;
|
||||
|
||||
if (pi->print_cb(d, r, GIT_DIFF_LINE_HUNK_HDR,
|
||||
@ -360,13 +360,14 @@ static int diff_print_patch_line(
|
||||
return 0;
|
||||
|
||||
git_buf_clear(pi->buf);
|
||||
git_buf_grow(pi->buf, content_len + 2);
|
||||
|
||||
if (line_origin == GIT_DIFF_LINE_ADDITION ||
|
||||
line_origin == GIT_DIFF_LINE_DELETION ||
|
||||
line_origin == GIT_DIFF_LINE_CONTEXT)
|
||||
git_buf_printf(pi->buf, "%c%.*s", line_origin, (int)content_len, content);
|
||||
else if (content_len > 0)
|
||||
git_buf_printf(pi->buf, "%.*s", (int)content_len, content);
|
||||
git_buf_putc(pi->buf, line_origin);
|
||||
|
||||
git_buf_put(pi->buf, content, content_len);
|
||||
|
||||
if (git_buf_oom(pi->buf))
|
||||
return -1;
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
*/
|
||||
#include "common.h"
|
||||
#include "fileops.h"
|
||||
#include "global.h"
|
||||
#include <ctype.h>
|
||||
#if GIT_WIN32
|
||||
#include "win32/findfile.h"
|
||||
@ -55,18 +56,8 @@ int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode
|
||||
|
||||
int git_futils_creat_locked(const char *path, const mode_t mode)
|
||||
{
|
||||
int fd;
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
git_win32_path buf;
|
||||
|
||||
git_win32_path_from_c(buf, path);
|
||||
fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC |
|
||||
int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC |
|
||||
O_EXCL | O_BINARY | O_CLOEXEC, mode);
|
||||
#else
|
||||
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC |
|
||||
O_EXCL | O_BINARY | O_CLOEXEC, mode);
|
||||
#endif
|
||||
|
||||
if (fd < 0) {
|
||||
giterr_set(GITERR_OS, "Failed to create locked file '%s'", path);
|
||||
@ -592,7 +583,7 @@ clean_up:
|
||||
static int git_futils_guess_system_dirs(git_buf *out)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
return git_win32__find_system_dirs(out);
|
||||
return git_win32__find_system_dirs(out, L"etc\\");
|
||||
#else
|
||||
return git_buf_sets(out, "/etc");
|
||||
#endif
|
||||
@ -624,17 +615,34 @@ static int git_futils_guess_xdg_dirs(git_buf *out)
|
||||
#endif
|
||||
}
|
||||
|
||||
static int git_futils_guess_template_dirs(git_buf *out)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
return git_win32__find_system_dirs(out, L"share\\git-core\\templates");
|
||||
#else
|
||||
return git_buf_sets(out, "/usr/share/git-core/templates");
|
||||
#endif
|
||||
}
|
||||
|
||||
typedef int (*git_futils_dirs_guess_cb)(git_buf *out);
|
||||
|
||||
static git_buf git_futils__dirs[GIT_FUTILS_DIR__MAX] =
|
||||
{ GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT };
|
||||
{ GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT };
|
||||
|
||||
static git_futils_dirs_guess_cb git_futils__dir_guess[GIT_FUTILS_DIR__MAX] = {
|
||||
git_futils_guess_system_dirs,
|
||||
git_futils_guess_global_dirs,
|
||||
git_futils_guess_xdg_dirs,
|
||||
git_futils_guess_template_dirs,
|
||||
};
|
||||
|
||||
static void git_futils_dirs_global_shutdown(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < GIT_FUTILS_DIR__MAX; ++i)
|
||||
git_buf_free(&git_futils__dirs[i]);
|
||||
}
|
||||
|
||||
int git_futils_dirs_global_init(void)
|
||||
{
|
||||
git_futils_dir_t i;
|
||||
@ -644,6 +652,8 @@ int git_futils_dirs_global_init(void)
|
||||
for (i = 0; !error && i < GIT_FUTILS_DIR__MAX; i++)
|
||||
error = git_futils_dirs_get(&path, i);
|
||||
|
||||
git__on_shutdown(git_futils_dirs_global_shutdown);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -726,13 +736,6 @@ int git_futils_dirs_set(git_futils_dir_t which, const char *search_path)
|
||||
return git_buf_oom(&git_futils__dirs[which]) ? -1 : 0;
|
||||
}
|
||||
|
||||
void git_futils_dirs_free(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < GIT_FUTILS_DIR__MAX; ++i)
|
||||
git_buf_free(&git_futils__dirs[i]);
|
||||
}
|
||||
|
||||
static int git_futils_find_in_dirlist(
|
||||
git_buf *path, const char *name, git_futils_dir_t which, const char *label)
|
||||
{
|
||||
@ -753,7 +756,8 @@ static int git_futils_find_in_dirlist(
|
||||
continue;
|
||||
|
||||
GITERR_CHECK_ERROR(git_buf_set(path, scan, len));
|
||||
GITERR_CHECK_ERROR(git_buf_joinpath(path, path->ptr, name));
|
||||
if (name)
|
||||
GITERR_CHECK_ERROR(git_buf_joinpath(path, path->ptr, name));
|
||||
|
||||
if (git_path_exists(path->ptr))
|
||||
return 0;
|
||||
@ -782,6 +786,12 @@ int git_futils_find_xdg_file(git_buf *path, const char *filename)
|
||||
path, filename, GIT_FUTILS_DIR_XDG, "global/xdg");
|
||||
}
|
||||
|
||||
int git_futils_find_template_dir(git_buf *path)
|
||||
{
|
||||
return git_futils_find_in_dirlist(
|
||||
path, NULL, GIT_FUTILS_DIR_TEMPLATE, "template");
|
||||
}
|
||||
|
||||
int git_futils_fake_symlink(const char *old, const char *new)
|
||||
{
|
||||
int retcode = GIT_ERROR;
|
||||
|
||||
@ -306,11 +306,20 @@ extern int git_futils_find_xdg_file(git_buf *path, const char *filename);
|
||||
*/
|
||||
extern int git_futils_find_system_file(git_buf *path, const char *filename);
|
||||
|
||||
/**
|
||||
* Find template directory.
|
||||
*
|
||||
* @param path buffer to write the full path into
|
||||
* @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
|
||||
*/
|
||||
extern int git_futils_find_template_dir(git_buf *path);
|
||||
|
||||
typedef enum {
|
||||
GIT_FUTILS_DIR_SYSTEM = 0,
|
||||
GIT_FUTILS_DIR_GLOBAL = 1,
|
||||
GIT_FUTILS_DIR_XDG = 2,
|
||||
GIT_FUTILS_DIR__MAX = 3,
|
||||
GIT_FUTILS_DIR_TEMPLATE = 3,
|
||||
GIT_FUTILS_DIR__MAX = 4,
|
||||
} git_futils_dir_t;
|
||||
|
||||
/**
|
||||
@ -353,11 +362,6 @@ extern int git_futils_dirs_get_str(
|
||||
*/
|
||||
extern int git_futils_dirs_set(git_futils_dir_t which, const char *paths);
|
||||
|
||||
/**
|
||||
* Release / reset all search paths
|
||||
*/
|
||||
extern void git_futils_dirs_free(void);
|
||||
|
||||
/**
|
||||
* Create a "fake" symlink (text file containing the target path).
|
||||
*
|
||||
|
||||
690
src/filter.c
690
src/filter.c
@ -10,68 +10,594 @@
|
||||
#include "hash.h"
|
||||
#include "filter.h"
|
||||
#include "repository.h"
|
||||
#include "global.h"
|
||||
#include "git2/sys/filter.h"
|
||||
#include "git2/config.h"
|
||||
#include "blob.h"
|
||||
#include "attr_file.h"
|
||||
#include "array.h"
|
||||
|
||||
int git_filters_load(git_vector *filters, git_repository *repo, const char *path, int mode)
|
||||
struct git_filter_source {
|
||||
git_repository *repo;
|
||||
const char *path;
|
||||
git_oid oid; /* zero if unknown (which is likely) */
|
||||
uint16_t filemode; /* zero if unknown */
|
||||
git_filter_mode_t mode;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
git_filter *filter;
|
||||
void *payload;
|
||||
} git_filter_entry;
|
||||
|
||||
struct git_filter_list {
|
||||
git_array_t(git_filter_entry) filters;
|
||||
git_filter_source source;
|
||||
char path[GIT_FLEX_ARRAY];
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char *filter_name;
|
||||
git_filter *filter;
|
||||
int priority;
|
||||
int initialized;
|
||||
size_t nattrs, nmatches;
|
||||
char *attrdata;
|
||||
const char *attrs[GIT_FLEX_ARRAY];
|
||||
} git_filter_def;
|
||||
|
||||
static int filter_def_priority_cmp(const void *a, const void *b)
|
||||
{
|
||||
int pa = ((const git_filter_def *)a)->priority;
|
||||
int pb = ((const git_filter_def *)b)->priority;
|
||||
return (pa < pb) ? -1 : (pa > pb) ? 1 : 0;
|
||||
}
|
||||
|
||||
struct filter_registry {
|
||||
git_vector filters;
|
||||
};
|
||||
|
||||
static struct filter_registry *git__filter_registry = NULL;
|
||||
|
||||
static void filter_registry_shutdown(void)
|
||||
{
|
||||
struct filter_registry *reg = NULL;
|
||||
size_t pos;
|
||||
git_filter_def *fdef;
|
||||
|
||||
if ((reg = git__swap(git__filter_registry, NULL)) == NULL)
|
||||
return;
|
||||
|
||||
git_vector_foreach(®->filters, pos, fdef) {
|
||||
if (fdef->initialized && fdef->filter && fdef->filter->shutdown) {
|
||||
fdef->filter->shutdown(fdef->filter);
|
||||
fdef->initialized = false;
|
||||
}
|
||||
|
||||
git__free(fdef->attrdata);
|
||||
git__free(fdef);
|
||||
}
|
||||
|
||||
git_vector_free(®->filters);
|
||||
git__free(reg);
|
||||
}
|
||||
|
||||
static int filter_registry_initialize(void)
|
||||
{
|
||||
int error = 0;
|
||||
struct filter_registry *reg;
|
||||
|
||||
if (git__filter_registry)
|
||||
return 0;
|
||||
|
||||
reg = git__calloc(1, sizeof(struct filter_registry));
|
||||
GITERR_CHECK_ALLOC(reg);
|
||||
|
||||
if ((error = git_vector_init(
|
||||
®->filters, 2, filter_def_priority_cmp)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
reg = git__compare_and_swap(&git__filter_registry, NULL, reg);
|
||||
if (reg != NULL)
|
||||
goto cleanup;
|
||||
|
||||
git__on_shutdown(filter_registry_shutdown);
|
||||
|
||||
/* try to register both default filters */
|
||||
{
|
||||
git_filter *crlf = git_crlf_filter_new();
|
||||
git_filter *ident = git_ident_filter_new();
|
||||
|
||||
if (crlf && git_filter_register(
|
||||
GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0)
|
||||
crlf = NULL;
|
||||
if (ident && git_filter_register(
|
||||
GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0)
|
||||
ident = NULL;
|
||||
|
||||
if (!crlf || !ident)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
cleanup:
|
||||
git_vector_free(®->filters);
|
||||
git__free(reg);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int filter_def_scan_attrs(
|
||||
git_buf *attrs, size_t *nattr, size_t *nmatch, const char *attr_str)
|
||||
{
|
||||
const char *start, *scan = attr_str;
|
||||
int has_eq;
|
||||
|
||||
*nattr = *nmatch = 0;
|
||||
|
||||
if (!scan)
|
||||
return 0;
|
||||
|
||||
while (*scan) {
|
||||
while (git__isspace(*scan)) scan++;
|
||||
|
||||
for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) {
|
||||
if (*scan == '=')
|
||||
has_eq = 1;
|
||||
}
|
||||
|
||||
if (scan > start) {
|
||||
(*nattr)++;
|
||||
if (has_eq || *start == '-' || *start == '+' || *start == '!')
|
||||
(*nmatch)++;
|
||||
|
||||
if (has_eq)
|
||||
git_buf_putc(attrs, '=');
|
||||
git_buf_put(attrs, start, scan - start);
|
||||
git_buf_putc(attrs, '\0');
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void filter_def_set_attrs(git_filter_def *fdef)
|
||||
{
|
||||
char *scan = fdef->attrdata;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < fdef->nattrs; ++i) {
|
||||
const char *name, *value;
|
||||
|
||||
switch (*scan) {
|
||||
case '=':
|
||||
name = scan + 1;
|
||||
for (scan++; *scan != '='; scan++) /* find '=' */;
|
||||
*scan++ = '\0';
|
||||
value = scan;
|
||||
break;
|
||||
case '-':
|
||||
name = scan + 1; value = git_attr__false; break;
|
||||
case '+':
|
||||
name = scan + 1; value = git_attr__true; break;
|
||||
case '!':
|
||||
name = scan + 1; value = git_attr__unset; break;
|
||||
default:
|
||||
name = scan; value = NULL; break;
|
||||
}
|
||||
|
||||
fdef->attrs[i] = name;
|
||||
fdef->attrs[i + fdef->nattrs] = value;
|
||||
|
||||
scan += strlen(scan) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int filter_def_name_key_check(const void *key, const void *fdef)
|
||||
{
|
||||
const char *name =
|
||||
fdef ? ((const git_filter_def *)fdef)->filter_name : NULL;
|
||||
return name ? git__strcmp(key, name) : -1;
|
||||
}
|
||||
|
||||
static int filter_def_filter_key_check(const void *key, const void *fdef)
|
||||
{
|
||||
const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL;
|
||||
return (key == filter) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int filter_registry_find(size_t *pos, const char *name)
|
||||
{
|
||||
return git_vector_search2(
|
||||
pos, &git__filter_registry->filters, filter_def_name_key_check, name);
|
||||
}
|
||||
|
||||
static git_filter_def *filter_registry_lookup(size_t *pos, const char *name)
|
||||
{
|
||||
git_filter_def *fdef = NULL;
|
||||
|
||||
if (!filter_registry_find(pos, name))
|
||||
fdef = git_vector_get(&git__filter_registry->filters, *pos);
|
||||
|
||||
return fdef;
|
||||
}
|
||||
|
||||
int git_filter_register(
|
||||
const char *name, git_filter *filter, int priority)
|
||||
{
|
||||
git_filter_def *fdef;
|
||||
size_t nattr = 0, nmatch = 0;
|
||||
git_buf attrs = GIT_BUF_INIT;
|
||||
|
||||
if (filter_registry_initialize() < 0)
|
||||
return -1;
|
||||
|
||||
if (!filter_registry_find(NULL, name)) {
|
||||
giterr_set(
|
||||
GITERR_FILTER, "Attempt to reregister existing filter '%s'", name);
|
||||
return GIT_EEXISTS;
|
||||
}
|
||||
|
||||
if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0)
|
||||
return -1;
|
||||
|
||||
fdef = git__calloc(
|
||||
sizeof(git_filter_def) + 2 * nattr * sizeof(char *), 1);
|
||||
GITERR_CHECK_ALLOC(fdef);
|
||||
|
||||
fdef->filter_name = name;
|
||||
fdef->filter = filter;
|
||||
fdef->priority = priority;
|
||||
fdef->nattrs = nattr;
|
||||
fdef->nmatches = nmatch;
|
||||
fdef->attrdata = git_buf_detach(&attrs);
|
||||
|
||||
filter_def_set_attrs(fdef);
|
||||
|
||||
if (git_vector_insert(&git__filter_registry->filters, fdef) < 0) {
|
||||
git__free(fdef->attrdata);
|
||||
git__free(fdef);
|
||||
return -1;
|
||||
}
|
||||
|
||||
git_vector_sort(&git__filter_registry->filters);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_filter_unregister(const char *name)
|
||||
{
|
||||
size_t pos;
|
||||
git_filter_def *fdef;
|
||||
|
||||
/* cannot unregister default filters */
|
||||
if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) {
|
||||
giterr_set(GITERR_FILTER, "Cannot unregister filter '%s'", name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((fdef = filter_registry_lookup(&pos, name)) == NULL) {
|
||||
giterr_set(GITERR_FILTER, "Cannot find filter '%s' to unregister", name);
|
||||
return GIT_ENOTFOUND;
|
||||
}
|
||||
|
||||
(void)git_vector_remove(&git__filter_registry->filters, pos);
|
||||
|
||||
if (fdef->initialized && fdef->filter && fdef->filter->shutdown) {
|
||||
fdef->filter->shutdown(fdef->filter);
|
||||
fdef->initialized = false;
|
||||
}
|
||||
|
||||
git__free(fdef->attrdata);
|
||||
git__free(fdef);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_initialize(git_filter_def *fdef)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
if (!fdef->initialized &&
|
||||
fdef->filter &&
|
||||
fdef->filter->initialize &&
|
||||
(error = fdef->filter->initialize(fdef->filter)) < 0)
|
||||
{
|
||||
/* auto-unregister if initialize fails */
|
||||
git_filter_unregister(fdef->filter_name);
|
||||
return error;
|
||||
}
|
||||
|
||||
fdef->initialized = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
git_filter *git_filter_lookup(const char *name)
|
||||
{
|
||||
size_t pos;
|
||||
git_filter_def *fdef;
|
||||
|
||||
if (filter_registry_initialize() < 0)
|
||||
return NULL;
|
||||
|
||||
if ((fdef = filter_registry_lookup(&pos, name)) == NULL)
|
||||
return NULL;
|
||||
|
||||
if (!fdef->initialized && filter_initialize(fdef) < 0)
|
||||
return NULL;
|
||||
|
||||
return fdef->filter;
|
||||
}
|
||||
|
||||
void git_filter_free(git_filter *filter)
|
||||
{
|
||||
git__free(filter);
|
||||
}
|
||||
|
||||
git_repository *git_filter_source_repo(const git_filter_source *src)
|
||||
{
|
||||
return src->repo;
|
||||
}
|
||||
|
||||
const char *git_filter_source_path(const git_filter_source *src)
|
||||
{
|
||||
return src->path;
|
||||
}
|
||||
|
||||
uint16_t git_filter_source_filemode(const git_filter_source *src)
|
||||
{
|
||||
return src->filemode;
|
||||
}
|
||||
|
||||
const git_oid *git_filter_source_id(const git_filter_source *src)
|
||||
{
|
||||
return git_oid_iszero(&src->oid) ? NULL : &src->oid;
|
||||
}
|
||||
|
||||
git_filter_mode_t git_filter_source_mode(const git_filter_source *src)
|
||||
{
|
||||
return src->mode;
|
||||
}
|
||||
|
||||
static int filter_list_new(
|
||||
git_filter_list **out, const git_filter_source *src)
|
||||
{
|
||||
git_filter_list *fl = NULL;
|
||||
size_t pathlen = src->path ? strlen(src->path) : 0;
|
||||
|
||||
fl = git__calloc(1, sizeof(git_filter_list) + pathlen + 1);
|
||||
GITERR_CHECK_ALLOC(fl);
|
||||
|
||||
if (src->path)
|
||||
memcpy(fl->path, src->path, pathlen);
|
||||
fl->source.repo = src->repo;
|
||||
fl->source.path = fl->path;
|
||||
fl->source.mode = src->mode;
|
||||
|
||||
*out = fl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_list_check_attributes(
|
||||
const char ***out, git_filter_def *fdef, const git_filter_source *src)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (mode == GIT_FILTER_TO_ODB) {
|
||||
/* Load the CRLF cleanup filter when writing to the ODB */
|
||||
error = git_filter_add__crlf_to_odb(filters, repo, path);
|
||||
if (error < 0)
|
||||
return error;
|
||||
} else {
|
||||
error = git_filter_add__crlf_to_workdir(filters, repo, path);
|
||||
if (error < 0)
|
||||
return error;
|
||||
}
|
||||
|
||||
return (int)filters->length;
|
||||
}
|
||||
|
||||
void git_filters_free(git_vector *filters)
|
||||
{
|
||||
size_t i;
|
||||
git_filter *filter;
|
||||
const char **strs = git__calloc(fdef->nattrs, sizeof(const char *));
|
||||
GITERR_CHECK_ALLOC(strs);
|
||||
|
||||
git_vector_foreach(filters, i, filter) {
|
||||
if (filter->do_free != NULL)
|
||||
filter->do_free(filter);
|
||||
else
|
||||
git__free(filter);
|
||||
}
|
||||
error = git_attr_get_many(
|
||||
strs, src->repo, 0, src->path, fdef->nattrs, fdef->attrs);
|
||||
|
||||
git_vector_free(filters);
|
||||
}
|
||||
|
||||
int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters)
|
||||
{
|
||||
size_t i;
|
||||
unsigned int src;
|
||||
git_buf *dbuffer[2];
|
||||
|
||||
dbuffer[0] = source;
|
||||
dbuffer[1] = dest;
|
||||
|
||||
src = 0;
|
||||
|
||||
if (git_buf_len(source) == 0) {
|
||||
git_buf_clear(dest);
|
||||
/* if no values were found but no matches are needed, it's okay! */
|
||||
if (error == GIT_ENOTFOUND && !fdef->nmatches) {
|
||||
giterr_clear();
|
||||
git__free((void *)strs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Pre-grow the destination buffer to more or less the size
|
||||
* we expect it to have */
|
||||
if (git_buf_grow(dest, git_buf_len(source)) < 0)
|
||||
for (i = 0; !error && i < fdef->nattrs; ++i) {
|
||||
const char *want = fdef->attrs[fdef->nattrs + i];
|
||||
git_attr_t want_type, found_type;
|
||||
|
||||
if (!want)
|
||||
continue;
|
||||
|
||||
want_type = git_attr_value(want);
|
||||
found_type = git_attr_value(strs[i]);
|
||||
|
||||
if (want_type != found_type ||
|
||||
(want_type == GIT_ATTR_VALUE_T && strcmp(want, strs[i])))
|
||||
error = GIT_ENOTFOUND;
|
||||
}
|
||||
|
||||
if (error)
|
||||
git__free((void *)strs);
|
||||
else
|
||||
*out = strs;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_filter_list_new(
|
||||
git_filter_list **out, git_repository *repo, git_filter_mode_t mode)
|
||||
{
|
||||
git_filter_source src = { 0 };
|
||||
src.repo = repo;
|
||||
src.path = NULL;
|
||||
src.mode = mode;
|
||||
return filter_list_new(out, &src);
|
||||
}
|
||||
|
||||
int git_filter_list_load(
|
||||
git_filter_list **filters,
|
||||
git_repository *repo,
|
||||
git_blob *blob, /* can be NULL */
|
||||
const char *path,
|
||||
git_filter_mode_t mode)
|
||||
{
|
||||
int error = 0;
|
||||
git_filter_list *fl = NULL;
|
||||
git_filter_source src = { 0 };
|
||||
git_filter_entry *fe;
|
||||
size_t idx;
|
||||
git_filter_def *fdef;
|
||||
|
||||
if (filter_registry_initialize() < 0)
|
||||
return -1;
|
||||
|
||||
for (i = 0; i < filters->length; ++i) {
|
||||
git_filter *filter = git_vector_get(filters, i);
|
||||
unsigned int dst = 1 - src;
|
||||
src.repo = repo;
|
||||
src.path = path;
|
||||
src.mode = mode;
|
||||
if (blob)
|
||||
git_oid_cpy(&src.oid, git_blob_id(blob));
|
||||
|
||||
git_buf_clear(dbuffer[dst]);
|
||||
git_vector_foreach(&git__filter_registry->filters, idx, fdef) {
|
||||
const char **values = NULL;
|
||||
void *payload = NULL;
|
||||
|
||||
if (!fdef || !fdef->filter)
|
||||
continue;
|
||||
|
||||
if (fdef->nattrs > 0) {
|
||||
error = filter_list_check_attributes(&values, fdef, &src);
|
||||
if (error == GIT_ENOTFOUND) {
|
||||
error = 0;
|
||||
continue;
|
||||
} else if (error < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
|
||||
break;
|
||||
|
||||
if (fdef->filter->check)
|
||||
error = fdef->filter->check(
|
||||
fdef->filter, &payload, &src, values);
|
||||
|
||||
git__free((void *)values);
|
||||
|
||||
if (error == GIT_PASSTHROUGH)
|
||||
error = 0;
|
||||
else if (error < 0)
|
||||
break;
|
||||
else {
|
||||
if (!fl && (error = filter_list_new(&fl, &src)) < 0)
|
||||
return error;
|
||||
|
||||
fe = git_array_alloc(fl->filters);
|
||||
GITERR_CHECK_ALLOC(fe);
|
||||
fe->filter = fdef->filter;
|
||||
fe->payload = payload;
|
||||
}
|
||||
}
|
||||
|
||||
if (error && fl != NULL) {
|
||||
git_array_clear(fl->filters);
|
||||
git__free(fl);
|
||||
fl = NULL;
|
||||
}
|
||||
|
||||
*filters = fl;
|
||||
return error;
|
||||
}
|
||||
|
||||
void git_filter_list_free(git_filter_list *fl)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
if (!fl)
|
||||
return;
|
||||
|
||||
for (i = 0; i < git_array_size(fl->filters); ++i) {
|
||||
git_filter_entry *fe = git_array_get(fl->filters, i);
|
||||
if (fe->filter->cleanup)
|
||||
fe->filter->cleanup(fe->filter, fe->payload);
|
||||
}
|
||||
|
||||
git_array_clear(fl->filters);
|
||||
git__free(fl);
|
||||
}
|
||||
|
||||
int git_filter_list_push(
|
||||
git_filter_list *fl, git_filter *filter, void *payload)
|
||||
{
|
||||
int error = 0;
|
||||
size_t pos;
|
||||
git_filter_def *fdef;
|
||||
git_filter_entry *fe;
|
||||
|
||||
assert(fl && filter);
|
||||
|
||||
if (git_vector_search2(
|
||||
&pos, &git__filter_registry->filters,
|
||||
filter_def_filter_key_check, filter) < 0) {
|
||||
giterr_set(GITERR_FILTER, "Cannot use an unregistered filter");
|
||||
return -1;
|
||||
}
|
||||
|
||||
fdef = git_vector_get(&git__filter_registry->filters, pos);
|
||||
|
||||
if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
|
||||
return error;
|
||||
|
||||
fe = git_array_alloc(fl->filters);
|
||||
GITERR_CHECK_ALLOC(fe);
|
||||
fe->filter = filter;
|
||||
fe->payload = payload;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t git_filter_list_length(const git_filter_list *fl)
|
||||
{
|
||||
return fl ? git_array_size(fl->filters) : 0;
|
||||
}
|
||||
|
||||
static int filter_list_out_buffer_from_raw(
|
||||
git_buf *out, const void *ptr, size_t size)
|
||||
{
|
||||
if (git_buf_is_allocated(out))
|
||||
git_buf_free(out);
|
||||
|
||||
if (!size) {
|
||||
git_buf_init(out, 0);
|
||||
} else {
|
||||
out->ptr = (char *)ptr;
|
||||
out->asize = 0;
|
||||
out->size = size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_filter_list_apply_to_data(
|
||||
git_buf *tgt, git_filter_list *fl, git_buf *src)
|
||||
{
|
||||
int error = 0;
|
||||
uint32_t i;
|
||||
git_buf *dbuffer[2], local = GIT_BUF_INIT;
|
||||
unsigned int si = 0;
|
||||
|
||||
if (!fl)
|
||||
return filter_list_out_buffer_from_raw(tgt, src->ptr, src->size);
|
||||
|
||||
dbuffer[0] = src;
|
||||
dbuffer[1] = tgt;
|
||||
|
||||
/* if `src` buffer is reallocable, then use it, otherwise copy it */
|
||||
if (!git_buf_is_allocated(src)) {
|
||||
if (git_buf_set(&local, src->ptr, src->size) < 0)
|
||||
return -1;
|
||||
dbuffer[0] = &local;
|
||||
}
|
||||
|
||||
for (i = 0; i < git_array_size(fl->filters); ++i) {
|
||||
unsigned int di = 1 - si;
|
||||
uint32_t fidx = (fl->source.mode == GIT_FILTER_TO_WORKTREE) ?
|
||||
i : git_array_size(fl->filters) - 1 - i;
|
||||
git_filter_entry *fe = git_array_get(fl->filters, fidx);
|
||||
|
||||
dbuffer[di]->size = 0;
|
||||
|
||||
/* Apply the filter from dbuffer[src] to the other buffer;
|
||||
* if the filtering is canceled by the user mid-filter,
|
||||
@ -79,16 +605,72 @@ int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters)
|
||||
* of the double buffering (so that the text goes through
|
||||
* cleanly).
|
||||
*/
|
||||
if (filter->apply(filter, dbuffer[dst], dbuffer[src]) == 0)
|
||||
src = dst;
|
||||
|
||||
if (git_buf_oom(dbuffer[dst]))
|
||||
return -1;
|
||||
error = fe->filter->apply(
|
||||
fe->filter, &fe->payload, dbuffer[di], dbuffer[si], &fl->source);
|
||||
|
||||
if (error == GIT_PASSTHROUGH) {
|
||||
/* PASSTHROUGH means filter decided not to process the buffer */
|
||||
error = 0;
|
||||
} else if (!error) {
|
||||
git_buf_shorten(dbuffer[di], 0); /* force NUL termination */
|
||||
si = di; /* swap buffers */
|
||||
} else {
|
||||
tgt->size = 0;
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure that the output ends up in dbuffer[1] (i.e. the dest) */
|
||||
if (src != 1)
|
||||
git_buf_swap(dest, source);
|
||||
if (si != 1)
|
||||
git_buf_swap(dbuffer[0], dbuffer[1]);
|
||||
|
||||
git_buf_free(&local); /* don't leak if we allocated locally */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_filter_list_apply_to_file(
|
||||
git_buf *out,
|
||||
git_filter_list *filters,
|
||||
git_repository *repo,
|
||||
const char *path)
|
||||
{
|
||||
int error;
|
||||
const char *base = repo ? git_repository_workdir(repo) : NULL;
|
||||
git_buf abspath = GIT_BUF_INIT, raw = GIT_BUF_INIT;
|
||||
|
||||
if (!(error = git_path_join_unrooted(&abspath, path, base, NULL)) &&
|
||||
!(error = git_futils_readbuffer(&raw, abspath.ptr)))
|
||||
{
|
||||
error = git_filter_list_apply_to_data(out, filters, &raw);
|
||||
|
||||
git_buf_free(&raw);
|
||||
}
|
||||
|
||||
git_buf_free(&abspath);
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_filter_list_apply_to_blob(
|
||||
git_buf *out,
|
||||
git_filter_list *filters,
|
||||
git_blob *blob)
|
||||
{
|
||||
git_buf in = GIT_BUF_INIT;
|
||||
git_off_t rawsize = git_blob_rawsize(blob);
|
||||
|
||||
if (!git__is_sizet(rawsize)) {
|
||||
giterr_set(GITERR_OS, "Blob is too large to filter");
|
||||
return -1;
|
||||
}
|
||||
|
||||
in.ptr = (char *)git_blob_rawcontent(blob);
|
||||
in.asize = 0;
|
||||
in.size = (size_t)rawsize;
|
||||
|
||||
if (filters)
|
||||
git_oid_cpy(&filters->source.oid, git_blob_id(blob));
|
||||
|
||||
return git_filter_list_apply_to_data(out, filters, &in);
|
||||
}
|
||||
|
||||
71
src/filter.h
71
src/filter.h
@ -8,19 +8,7 @@
|
||||
#define INCLUDE_filter_h__
|
||||
|
||||
#include "common.h"
|
||||
#include "buffer.h"
|
||||
#include "git2/odb.h"
|
||||
#include "git2/repository.h"
|
||||
|
||||
typedef struct git_filter {
|
||||
int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source);
|
||||
void (*do_free)(struct git_filter *self);
|
||||
} git_filter;
|
||||
|
||||
typedef enum {
|
||||
GIT_FILTER_TO_WORKTREE,
|
||||
GIT_FILTER_TO_ODB
|
||||
} git_filter_mode;
|
||||
#include "git2/filter.h"
|
||||
|
||||
typedef enum {
|
||||
GIT_CRLF_GUESS = -1,
|
||||
@ -31,64 +19,13 @@ typedef enum {
|
||||
GIT_CRLF_AUTO,
|
||||
} git_crlf_t;
|
||||
|
||||
/*
|
||||
* FILTER API
|
||||
*/
|
||||
|
||||
/*
|
||||
* For any given path in the working directory, fill the `filters`
|
||||
* array with the relevant filters that need to be applied.
|
||||
*
|
||||
* Mode is either `GIT_FILTER_TO_WORKTREE` if you need to load the
|
||||
* filters that will be used when checking out a file to the working
|
||||
* directory, or `GIT_FILTER_TO_ODB` for the filters used when writing
|
||||
* a file to the ODB.
|
||||
*
|
||||
* @param filters Vector where to store all the loaded filters
|
||||
* @param repo Repository object that contains `path`
|
||||
* @param path Relative path of the file to be filtered
|
||||
* @param mode Filtering direction (WT->ODB or ODB->WT)
|
||||
* @return the number of filters loaded for the file (0 if the file
|
||||
* doesn't need filtering), or a negative error code
|
||||
*/
|
||||
extern int git_filters_load(git_vector *filters, git_repository *repo, const char *path, int mode);
|
||||
|
||||
/*
|
||||
* Apply one or more filters to a file.
|
||||
*
|
||||
* The file must have been loaded as a `git_buf` object. Both the `source`
|
||||
* and `dest` buffers are owned by the caller and must be freed once
|
||||
* they are no longer needed.
|
||||
*
|
||||
* NOTE: Because of the double-buffering schema, the `source` buffer that contains
|
||||
* the original file may be tampered once the filtering is complete. Regardless,
|
||||
* the `dest` buffer will always contain the final result of the filtering
|
||||
*
|
||||
* @param dest Buffer to store the result of the filtering
|
||||
* @param source Buffer containing the document to filter
|
||||
* @param filters A non-empty vector of filters as supplied by `git_filters_load`
|
||||
* @return 0 on success, an error code otherwise
|
||||
*/
|
||||
extern int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters);
|
||||
|
||||
/*
|
||||
* Free the `filters` array generated by `git_filters_load`.
|
||||
*
|
||||
* Note that this frees both the array and its contents. The array will
|
||||
* be clean/reusable after this call.
|
||||
*
|
||||
* @param filters A filters array as supplied by `git_filters_load`
|
||||
*/
|
||||
extern void git_filters_free(git_vector *filters);
|
||||
extern void git_filter_free(git_filter *filter);
|
||||
|
||||
/*
|
||||
* Available filters
|
||||
*/
|
||||
|
||||
/* Strip CRLF, from Worktree to ODB */
|
||||
extern int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path);
|
||||
|
||||
/* Add CRLF, from ODB to worktree */
|
||||
extern int git_filter_add__crlf_to_workdir(git_vector *filters, git_repository *repo, const char *path);
|
||||
extern git_filter *git_crlf_filter_new(void);
|
||||
extern git_filter *git_ident_filter_new(void);
|
||||
|
||||
#endif
|
||||
|
||||
38
src/global.c
38
src/global.c
@ -14,6 +14,28 @@
|
||||
|
||||
git_mutex git__mwindow_mutex;
|
||||
|
||||
#define MAX_SHUTDOWN_CB 8
|
||||
|
||||
git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB];
|
||||
git_atomic git__n_shutdown_callbacks;
|
||||
|
||||
void git__on_shutdown(git_global_shutdown_fn callback)
|
||||
{
|
||||
int count = git_atomic_inc(&git__n_shutdown_callbacks);
|
||||
assert(count <= MAX_SHUTDOWN_CB);
|
||||
git__shutdown_callbacks[count - 1] = callback;
|
||||
}
|
||||
|
||||
static void git__shutdown(void)
|
||||
{
|
||||
int pos;
|
||||
|
||||
while ((pos = git_atomic_dec(&git__n_shutdown_callbacks)) >= 0) {
|
||||
if (git__shutdown_callbacks[pos])
|
||||
git__shutdown_callbacks[pos]();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the global state with TLS
|
||||
*
|
||||
@ -79,9 +101,7 @@ int git_threads_init(void)
|
||||
void git_threads_shutdown(void)
|
||||
{
|
||||
/* Shut down any subsystems that have global state */
|
||||
win32_pthread_shutdown();
|
||||
git_futils_dirs_free();
|
||||
git_hash_global_shutdown();
|
||||
git__shutdown();
|
||||
|
||||
TlsFree(_tls_index);
|
||||
_tls_init = 0;
|
||||
@ -140,6 +160,9 @@ int git_threads_init(void)
|
||||
|
||||
void git_threads_shutdown(void)
|
||||
{
|
||||
/* Shut down any subsystems that have global state */
|
||||
git__shutdown();
|
||||
|
||||
if (_tls_init) {
|
||||
void *ptr = pthread_getspecific(_tls_key);
|
||||
pthread_setspecific(_tls_key, NULL);
|
||||
@ -149,10 +172,6 @@ void git_threads_shutdown(void)
|
||||
pthread_key_delete(_tls_key);
|
||||
_tls_init = 0;
|
||||
git_mutex_free(&git__mwindow_mutex);
|
||||
|
||||
/* Shut down any subsystems that have global state */
|
||||
git_hash_global_shutdown();
|
||||
git_futils_dirs_free();
|
||||
}
|
||||
|
||||
git_global_st *git__global_state(void)
|
||||
@ -179,15 +198,14 @@ static git_global_st __state;
|
||||
|
||||
int git_threads_init(void)
|
||||
{
|
||||
/* noop */
|
||||
/* noop */
|
||||
return 0;
|
||||
}
|
||||
|
||||
void git_threads_shutdown(void)
|
||||
{
|
||||
/* Shut down any subsystems that have global state */
|
||||
git_hash_global_shutdown();
|
||||
git_futils_dirs_free();
|
||||
git__shutdown();
|
||||
}
|
||||
|
||||
git_global_st *git__global_state(void)
|
||||
|
||||
@ -21,4 +21,8 @@ extern git_mutex git__mwindow_mutex;
|
||||
|
||||
#define GIT_GLOBAL (git__global_state())
|
||||
|
||||
typedef void (*git_global_shutdown_fn)(void);
|
||||
|
||||
extern void git__on_shutdown(git_global_shutdown_fn callback);
|
||||
|
||||
#endif
|
||||
|
||||
@ -13,8 +13,6 @@ typedef struct git_hash_prov git_hash_prov;
|
||||
typedef struct git_hash_ctx git_hash_ctx;
|
||||
|
||||
int git_hash_global_init(void);
|
||||
void git_hash_global_shutdown(void);
|
||||
|
||||
int git_hash_ctx_init(git_hash_ctx *ctx);
|
||||
void git_hash_ctx_cleanup(git_hash_ctx *ctx);
|
||||
|
||||
|
||||
@ -17,7 +17,6 @@ struct git_hash_ctx {
|
||||
};
|
||||
|
||||
#define git_hash_global_init() 0
|
||||
#define git_hash_global_shutdown() /* noop */
|
||||
#define git_hash_ctx_init(ctx) git_hash_init(ctx)
|
||||
#define git_hash_ctx_cleanup(ctx)
|
||||
|
||||
|
||||
@ -17,7 +17,6 @@ struct git_hash_ctx {
|
||||
};
|
||||
|
||||
#define git_hash_global_init() 0
|
||||
#define git_hash_global_shutdown() /* noop */
|
||||
#define git_hash_ctx_init(ctx) git_hash_init(ctx)
|
||||
#define git_hash_ctx_cleanup(ctx)
|
||||
|
||||
|
||||
@ -89,7 +89,15 @@ GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void)
|
||||
hash_prov.type = INVALID;
|
||||
}
|
||||
|
||||
int git_hash_global_init()
|
||||
static void git_hash_global_shutdown(void)
|
||||
{
|
||||
if (hash_prov.type == CNG)
|
||||
hash_cng_prov_shutdown();
|
||||
else if(hash_prov.type == CRYPTOAPI)
|
||||
hash_cryptoapi_prov_shutdown();
|
||||
}
|
||||
|
||||
int git_hash_global_init(void)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
@ -99,15 +107,9 @@ int git_hash_global_init()
|
||||
if ((error = hash_cng_prov_init()) < 0)
|
||||
error = hash_cryptoapi_prov_init();
|
||||
|
||||
return error;
|
||||
}
|
||||
git__on_shutdown(git_hash_global_shutdown);
|
||||
|
||||
void git_hash_global_shutdown()
|
||||
{
|
||||
if (hash_prov.type == CNG)
|
||||
hash_cng_prov_shutdown();
|
||||
else if(hash_prov.type == CRYPTOAPI)
|
||||
hash_cryptoapi_prov_shutdown();
|
||||
return error;
|
||||
}
|
||||
|
||||
/* CryptoAPI: available in Windows XP and newer */
|
||||
|
||||
125
src/ident.c
Normal file
125
src/ident.c
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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 "git2/sys/filter.h"
|
||||
#include "filter.h"
|
||||
#include "buffer.h"
|
||||
#include "buf_text.h"
|
||||
|
||||
static int ident_find_id(
|
||||
const char **id_start, const char **id_end, const char *start, size_t len)
|
||||
{
|
||||
const char *end = start + len, *found = NULL;
|
||||
|
||||
while (len > 3 && (found = memchr(start, '$', len)) != NULL) {
|
||||
size_t remaining = (size_t)(end - found) - 1;
|
||||
if (remaining < 3)
|
||||
return GIT_ENOTFOUND;
|
||||
|
||||
start = found + 1;
|
||||
len = remaining;
|
||||
|
||||
if (start[0] == 'I' && start[1] == 'd')
|
||||
break;
|
||||
}
|
||||
|
||||
if (len < 3 || !found)
|
||||
return GIT_ENOTFOUND;
|
||||
*id_start = found;
|
||||
|
||||
if ((found = memchr(start + 2, '$', len - 2)) == NULL)
|
||||
return GIT_ENOTFOUND;
|
||||
|
||||
*id_end = found + 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ident_insert_id(
|
||||
git_buf *to, const git_buf *from, const git_filter_source *src)
|
||||
{
|
||||
char oid[GIT_OID_HEXSZ+1];
|
||||
const char *id_start, *id_end, *from_end = from->ptr + from->size;
|
||||
size_t need_size;
|
||||
|
||||
/* replace $Id$ with blob id */
|
||||
|
||||
if (!git_filter_source_id(src))
|
||||
return GIT_PASSTHROUGH;
|
||||
|
||||
git_oid_tostr(oid, sizeof(oid), git_filter_source_id(src));
|
||||
|
||||
if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0)
|
||||
return GIT_PASSTHROUGH;
|
||||
|
||||
need_size = (size_t)(id_start - from->ptr) +
|
||||
5 /* "$Id: " */ + GIT_OID_HEXSZ + 1 /* "$" */ +
|
||||
(size_t)(from_end - id_end);
|
||||
|
||||
if (git_buf_grow(to, need_size) < 0)
|
||||
return -1;
|
||||
|
||||
git_buf_set(to, from->ptr, (size_t)(id_start - from->ptr));
|
||||
git_buf_put(to, "$Id: ", 5);
|
||||
git_buf_put(to, oid, GIT_OID_HEXSZ);
|
||||
git_buf_putc(to, '$');
|
||||
git_buf_put(to, id_end, (size_t)(from_end - id_end));
|
||||
|
||||
return git_buf_oom(to) ? -1 : 0;
|
||||
}
|
||||
|
||||
static int ident_remove_id(
|
||||
git_buf *to, const git_buf *from)
|
||||
{
|
||||
const char *id_start, *id_end, *from_end = from->ptr + from->size;
|
||||
size_t need_size;
|
||||
|
||||
if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0)
|
||||
return GIT_PASSTHROUGH;
|
||||
|
||||
need_size = (size_t)(id_start - from->ptr) +
|
||||
4 /* "$Id$" */ + (size_t)(from_end - id_end);
|
||||
|
||||
if (git_buf_grow(to, need_size) < 0)
|
||||
return -1;
|
||||
|
||||
git_buf_set(to, from->ptr, (size_t)(id_start - from->ptr));
|
||||
git_buf_put(to, "$Id$", 4);
|
||||
git_buf_put(to, id_end, (size_t)(from_end - id_end));
|
||||
|
||||
return git_buf_oom(to) ? -1 : 0;
|
||||
}
|
||||
|
||||
static int ident_apply(
|
||||
git_filter *self,
|
||||
void **payload,
|
||||
git_buf *to,
|
||||
const git_buf *from,
|
||||
const git_filter_source *src)
|
||||
{
|
||||
GIT_UNUSED(self); GIT_UNUSED(payload);
|
||||
|
||||
/* Don't filter binary files */
|
||||
if (git_buf_text_is_binary(from))
|
||||
return GIT_PASSTHROUGH;
|
||||
|
||||
if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
|
||||
return ident_insert_id(to, from, src);
|
||||
else
|
||||
return ident_remove_id(to, from);
|
||||
}
|
||||
|
||||
git_filter *git_ident_filter_new(void)
|
||||
{
|
||||
git_filter *f = git__calloc(1, sizeof(git_filter));
|
||||
|
||||
f->version = GIT_FILTER_VERSION;
|
||||
f->attributes = "+ident"; /* apply to files with ident attribute set */
|
||||
f->shutdown = git_filter_free;
|
||||
f->apply = ident_apply;
|
||||
|
||||
return f;
|
||||
}
|
||||
@ -410,7 +410,7 @@ static int create_index_error(int error, const char *msg)
|
||||
|
||||
int git_index_set_caps(git_index *index, unsigned int caps)
|
||||
{
|
||||
int old_ignore_case;
|
||||
unsigned int old_ignore_case;
|
||||
|
||||
assert(index);
|
||||
|
||||
@ -438,7 +438,7 @@ int git_index_set_caps(git_index *index, unsigned int caps)
|
||||
}
|
||||
|
||||
if (old_ignore_case != index->ignore_case) {
|
||||
git_index__set_ignore_case(index, index->ignore_case);
|
||||
git_index__set_ignore_case(index, (bool)index->ignore_case);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -2092,7 +2092,7 @@ int git_index_add_all(
|
||||
|
||||
/* check if path actually matches */
|
||||
if (!git_pathspec__match(
|
||||
&ps.pathspec, wd->path, no_fnmatch, ignorecase, &match, NULL))
|
||||
&ps.pathspec, wd->path, no_fnmatch, (bool)ignorecase, &match, NULL))
|
||||
continue;
|
||||
|
||||
/* skip ignored items that are not already in the index */
|
||||
@ -2184,7 +2184,7 @@ static int index_apply_to_all(
|
||||
|
||||
/* check if path actually matches */
|
||||
if (!git_pathspec__match(
|
||||
&ps.pathspec, entry->path, false, index->ignore_case,
|
||||
&ps.pathspec, entry->path, false, (bool)index->ignore_case,
|
||||
&match, NULL))
|
||||
continue;
|
||||
|
||||
|
||||
@ -47,6 +47,11 @@ struct git_indexer_stream {
|
||||
git_transfer_progress_callback progress_cb;
|
||||
void *progress_payload;
|
||||
char objbuf[8*1024];
|
||||
|
||||
/* Fields for calculating the packfile trailer (hash of everything before it) */
|
||||
char inbuf[GIT_OID_RAWSZ];
|
||||
int inbuf_len;
|
||||
git_hash_ctx trailer;
|
||||
};
|
||||
|
||||
struct delta_info {
|
||||
@ -121,6 +126,7 @@ int git_indexer_stream_new(
|
||||
GITERR_CHECK_ALLOC(idx);
|
||||
idx->progress_cb = progress_cb;
|
||||
idx->progress_payload = progress_payload;
|
||||
git_hash_ctx_init(&idx->trailer);
|
||||
|
||||
error = git_buf_joinpath(&path, prefix, suff);
|
||||
if (error < 0)
|
||||
@ -322,7 +328,6 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent
|
||||
entry->offset = (uint32_t)entry_start;
|
||||
}
|
||||
|
||||
/* FIXME: Parse the object instead of hashing it */
|
||||
if (git_odb__hashobj(&oid, obj) < 0) {
|
||||
giterr_set(GITERR_INDEXER, "Failed to hash object");
|
||||
goto on_error;
|
||||
@ -370,6 +375,43 @@ static int do_progress_callback(git_indexer_stream *idx, git_transfer_progress *
|
||||
return idx->progress_cb(stats, idx->progress_payload);
|
||||
}
|
||||
|
||||
/* Hash everything but the last 20B of input */
|
||||
static void hash_partially(git_indexer_stream *idx, const uint8_t *data, size_t size)
|
||||
{
|
||||
int to_expell, to_keep;
|
||||
|
||||
if (size == 0)
|
||||
return;
|
||||
|
||||
/* Easy case, dump the buffer and the data minus the last 20 bytes */
|
||||
if (size >= 20) {
|
||||
git_hash_update(&idx->trailer, idx->inbuf, idx->inbuf_len);
|
||||
git_hash_update(&idx->trailer, data, size - GIT_OID_RAWSZ);
|
||||
|
||||
data += size - GIT_OID_RAWSZ;
|
||||
memcpy(idx->inbuf, data, GIT_OID_RAWSZ);
|
||||
idx->inbuf_len = GIT_OID_RAWSZ;
|
||||
return;
|
||||
}
|
||||
|
||||
/* We can just append */
|
||||
if (idx->inbuf_len + size <= GIT_OID_RAWSZ) {
|
||||
memcpy(idx->inbuf + idx->inbuf_len, data, size);
|
||||
idx->inbuf_len += size;
|
||||
return;
|
||||
}
|
||||
|
||||
/* We need to partially drain the buffer and then append */
|
||||
to_expell = abs(size - (GIT_OID_RAWSZ - idx->inbuf_len));
|
||||
to_keep = abs(idx->inbuf_len - to_expell);
|
||||
|
||||
git_hash_update(&idx->trailer, idx->inbuf, to_expell);
|
||||
|
||||
memmove(idx->inbuf, idx->inbuf + to_expell, to_keep);
|
||||
memcpy(idx->inbuf + to_keep, data, size);
|
||||
idx->inbuf_len += size - to_expell;
|
||||
}
|
||||
|
||||
int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_transfer_progress *stats)
|
||||
{
|
||||
int error = -1;
|
||||
@ -384,6 +426,8 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
|
||||
if (git_filebuf_write(&idx->pack_file, data, size) < 0)
|
||||
return -1;
|
||||
|
||||
hash_partially(idx, data, size);
|
||||
|
||||
/* Make sure we set the new size of the pack */
|
||||
if (idx->opened_pack) {
|
||||
idx->pack->mwf.size += size;
|
||||
@ -576,17 +620,33 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *
|
||||
struct git_pack_idx_header hdr;
|
||||
git_buf filename = GIT_BUF_INIT;
|
||||
struct entry *entry;
|
||||
void *packfile_hash;
|
||||
git_oid file_hash;
|
||||
git_oid trailer_hash, file_hash;
|
||||
git_hash_ctx ctx;
|
||||
git_filebuf index_file = {0};
|
||||
void *packfile_trailer;
|
||||
|
||||
if (git_hash_ctx_init(&ctx) < 0)
|
||||
return -1;
|
||||
|
||||
/* Test for this before resolve_deltas(), as it plays with idx->off */
|
||||
if (idx->off < idx->pack->mwf.size - GIT_OID_RAWSZ) {
|
||||
giterr_set(GITERR_INDEXER, "Indexing error: unexpected data at the end of the pack");
|
||||
if (idx->off < idx->pack->mwf.size - 20) {
|
||||
giterr_set(GITERR_INDEXER, "unexpected data at the end of the pack");
|
||||
return -1;
|
||||
}
|
||||
|
||||
packfile_trailer = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ, &left);
|
||||
if (packfile_trailer == NULL) {
|
||||
git_mwindow_close(&w);
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
/* Compare the packfile trailer as it was sent to us and what we calculated */
|
||||
git_oid_fromraw(&file_hash, packfile_trailer);
|
||||
git_mwindow_close(&w);
|
||||
|
||||
git_hash_final(&trailer_hash, &idx->trailer);
|
||||
if (git_oid_cmp(&file_hash, &trailer_hash)) {
|
||||
giterr_set(GITERR_INDEXER, "packfile trailer mismatch");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -595,7 +655,7 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *
|
||||
return -1;
|
||||
|
||||
if (stats->indexed_objects != stats->total_objects) {
|
||||
giterr_set(GITERR_INDEXER, "Indexing error: early EOF");
|
||||
giterr_set(GITERR_INDEXER, "early EOF");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -658,23 +718,15 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *
|
||||
git_filebuf_write(&index_file, &split, sizeof(uint32_t) * 2);
|
||||
}
|
||||
|
||||
/* Write out the packfile trailer */
|
||||
packfile_hash = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ, &left);
|
||||
if (packfile_hash == NULL) {
|
||||
git_mwindow_close(&w);
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
memcpy(&file_hash, packfile_hash, GIT_OID_RAWSZ);
|
||||
git_mwindow_close(&w);
|
||||
|
||||
git_filebuf_write(&index_file, &file_hash, sizeof(git_oid));
|
||||
|
||||
/* Write out the packfile trailer to the idx file as well */
|
||||
if (git_filebuf_hash(&file_hash, &index_file) < 0)
|
||||
/* Write out the packfile trailer to the index */
|
||||
if (git_filebuf_write(&index_file, &trailer_hash, GIT_OID_RAWSZ) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_filebuf_write(&index_file, &file_hash, sizeof(git_oid));
|
||||
/* Write out the hash of the idx */
|
||||
if (git_filebuf_hash(&trailer_hash, &index_file) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_filebuf_write(&index_file, &trailer_hash, sizeof(git_oid));
|
||||
|
||||
/* Figure out what the final name should be */
|
||||
if (index_path_stream(&filename, idx, ".idx") < 0)
|
||||
|
||||
@ -58,7 +58,7 @@ struct merge_diff_df_data {
|
||||
|
||||
/* Merge base computation */
|
||||
|
||||
int git_merge_base_many(git_oid *out, git_repository *repo, const git_oid input_array[], size_t length)
|
||||
int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[])
|
||||
{
|
||||
git_revwalk *walk;
|
||||
git_vector list;
|
||||
|
||||
@ -585,7 +585,6 @@ int gitno_extract_url_parts(
|
||||
const char *start;
|
||||
|
||||
/*
|
||||
*
|
||||
* ==> [user[:pass]@]hostname.tld[:port]/resource
|
||||
*/
|
||||
|
||||
|
||||
23
src/odb.c
23
src/odb.c
@ -168,7 +168,6 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type)
|
||||
error = -1;
|
||||
|
||||
goto done;
|
||||
return -1;
|
||||
}
|
||||
|
||||
error = git_hash_final(out, &ctx);
|
||||
@ -179,28 +178,30 @@ done:
|
||||
}
|
||||
|
||||
int git_odb__hashfd_filtered(
|
||||
git_oid *out, git_file fd, size_t size, git_otype type, git_vector *filters)
|
||||
git_oid *out, git_file fd, size_t size, git_otype type, git_filter_list *fl)
|
||||
{
|
||||
int error;
|
||||
git_buf raw = GIT_BUF_INIT;
|
||||
git_buf filtered = GIT_BUF_INIT;
|
||||
|
||||
if (!filters || !filters->length)
|
||||
if (!fl)
|
||||
return git_odb__hashfd(out, fd, size, type);
|
||||
|
||||
/* size of data is used in header, so we have to read the whole file
|
||||
* into memory to apply filters before beginning to calculate the hash
|
||||
*/
|
||||
|
||||
if (!(error = git_futils_readbuffer_fd(&raw, fd, size)))
|
||||
error = git_filters_apply(&filtered, &raw, filters);
|
||||
if (!(error = git_futils_readbuffer_fd(&raw, fd, size))) {
|
||||
git_buf post = GIT_BUF_INIT;
|
||||
|
||||
git_buf_free(&raw);
|
||||
error = git_filter_list_apply_to_data(&post, fl, &raw);
|
||||
|
||||
if (!error)
|
||||
error = git_odb_hash(out, filtered.ptr, filtered.size, type);
|
||||
git_buf_free(&raw);
|
||||
|
||||
git_buf_free(&filtered);
|
||||
if (!error)
|
||||
error = git_odb_hash(out, post.ptr, post.size, type);
|
||||
|
||||
git_buf_free(&post);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
@ -621,7 +622,7 @@ int git_odb_exists(git_odb *db, const git_oid *id)
|
||||
git_odb_backend *b = internal->backend;
|
||||
|
||||
if (b->exists != NULL)
|
||||
found = b->exists(b, id);
|
||||
found = (bool)b->exists(b, id);
|
||||
}
|
||||
|
||||
return (int)found;
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
#include "vector.h"
|
||||
#include "cache.h"
|
||||
#include "posix.h"
|
||||
#include "filter.h"
|
||||
|
||||
#define GIT_OBJECTS_DIR "objects/"
|
||||
#define GIT_OBJECT_DIR_MODE 0777
|
||||
@ -66,7 +67,7 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type);
|
||||
* Acts just like git_odb__hashfd with the addition of filters...
|
||||
*/
|
||||
int git_odb__hashfd_filtered(
|
||||
git_oid *out, git_file fd, size_t len, git_otype type, git_vector *filters);
|
||||
git_oid *out, git_file fd, size_t len, git_otype type, git_filter_list *fl);
|
||||
|
||||
/*
|
||||
* Hash a `path`, assuming it could be a POSIX symlink: if the path is a
|
||||
|
||||
@ -211,7 +211,7 @@ int git_oid_strcmp(const git_oid *oid_a, const char *str)
|
||||
for (a = oid_a->id; *str && (a - oid_a->id) < GIT_OID_RAWSZ; ++a) {
|
||||
if ((hexval = git__fromhex(*str++)) < 0)
|
||||
return -1;
|
||||
strval = hexval << 4;
|
||||
strval = (unsigned char)(hexval << 4);
|
||||
if (*str) {
|
||||
if ((hexval = git__fromhex(*str++)) < 0)
|
||||
return -1;
|
||||
|
||||
12
src/path.c
12
src/path.c
@ -565,7 +565,7 @@ static bool _check_dir_contents(
|
||||
size_t sub_size = strlen(sub);
|
||||
|
||||
/* leave base valid even if we could not make space for subdir */
|
||||
if (git_buf_try_grow(dir, dir_size + sub_size + 2, false) < 0)
|
||||
if (git_buf_try_grow(dir, dir_size + sub_size + 2, false, false) < 0)
|
||||
return false;
|
||||
|
||||
/* save excursion */
|
||||
@ -902,8 +902,16 @@ int git_path_dirload_with_stat(
|
||||
git_buf_truncate(&full, prefix_len);
|
||||
|
||||
if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 ||
|
||||
(error = git_path_lstat(full.ptr, &ps->st)) < 0)
|
||||
(error = git_path_lstat(full.ptr, &ps->st)) < 0) {
|
||||
if (error == GIT_ENOTFOUND) {
|
||||
giterr_clear();
|
||||
error = 0;
|
||||
git_vector_remove(contents, i--);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (S_ISDIR(ps->st.st_mode)) {
|
||||
if ((error = git_buf_joinpath(&full, full.ptr, ".git")) < 0)
|
||||
|
||||
@ -39,7 +39,7 @@ typedef int git_file;
|
||||
*
|
||||
* Some of the methods are slightly wrapped to provide
|
||||
* saner defaults. Some of these methods are emulated
|
||||
* in Windows platforns.
|
||||
* in Windows platforms.
|
||||
*
|
||||
* Use your manpages to check the docs on these.
|
||||
*/
|
||||
|
||||
@ -201,5 +201,5 @@ int git_refdb_rename(
|
||||
int git_refdb_delete(struct git_refdb *db, const char *ref_name)
|
||||
{
|
||||
assert(db && db->backend);
|
||||
return db->backend->delete(db->backend, ref_name);
|
||||
return db->backend->del(db->backend, ref_name);
|
||||
}
|
||||
|
||||
@ -1096,7 +1096,7 @@ int git_refdb_backend_fs(
|
||||
backend->parent.lookup = &refdb_fs_backend__lookup;
|
||||
backend->parent.iterator = &refdb_fs_backend__iterator;
|
||||
backend->parent.write = &refdb_fs_backend__write;
|
||||
backend->parent.delete = &refdb_fs_backend__delete;
|
||||
backend->parent.del = &refdb_fs_backend__delete;
|
||||
backend->parent.rename = &refdb_fs_backend__rename;
|
||||
backend->parent.compress = &refdb_fs_backend__compress;
|
||||
backend->parent.free = &refdb_fs_backend__free;
|
||||
|
||||
50
src/remote.c
50
src/remote.c
@ -18,8 +18,6 @@
|
||||
#include "refspec.h"
|
||||
#include "fetchhead.h"
|
||||
|
||||
#include <regex.h>
|
||||
|
||||
static int add_refspec(git_remote *remote, const char *string, bool is_fetch)
|
||||
{
|
||||
git_refspec *spec;
|
||||
@ -362,7 +360,7 @@ cleanup:
|
||||
static int update_config_refspec(const git_remote *remote, git_config *config, int direction)
|
||||
{
|
||||
git_buf name = GIT_BUF_INIT;
|
||||
int push;
|
||||
unsigned int push;
|
||||
const char *dir;
|
||||
size_t i;
|
||||
int error = 0;
|
||||
@ -806,7 +804,7 @@ static int remote_head_for_ref(git_remote_head **out, git_refspec *spec, git_vec
|
||||
(!git_reference_is_branch(resolved_ref)) ||
|
||||
(error = git_branch_upstream(&tracking_ref, resolved_ref)) < 0 ||
|
||||
(error = git_refspec_transform_l(&remote_name, spec, git_reference_name(tracking_ref))) < 0) {
|
||||
/* Not an error if HEAD is orphaned or no tracking branch */
|
||||
/* Not an error if HEAD is unborn or no tracking branch */
|
||||
if (error == GIT_ENOTFOUND)
|
||||
error = 0;
|
||||
|
||||
@ -1075,35 +1073,28 @@ void git_remote_free(git_remote *remote)
|
||||
git__free(remote);
|
||||
}
|
||||
|
||||
struct cb_data {
|
||||
git_vector *list;
|
||||
regex_t *preg;
|
||||
};
|
||||
|
||||
static int remote_list_cb(const git_config_entry *entry, void *data_)
|
||||
static int remote_list_cb(const git_config_entry *entry, void *payload)
|
||||
{
|
||||
struct cb_data *data = (struct cb_data *)data_;
|
||||
size_t nmatch = 2;
|
||||
regmatch_t pmatch[2];
|
||||
const char *name = entry->name;
|
||||
git_vector *list = payload;
|
||||
const char *name = entry->name + strlen("remote.");
|
||||
size_t namelen = strlen(name);
|
||||
char *remote_name;
|
||||
|
||||
if (!regexec(data->preg, name, nmatch, pmatch, 0)) {
|
||||
char *remote_name = git__strndup(&name[pmatch[1].rm_so], pmatch[1].rm_eo - pmatch[1].rm_so);
|
||||
GITERR_CHECK_ALLOC(remote_name);
|
||||
/* we know name matches "remote.<stuff>.(push)?url" */
|
||||
|
||||
if (git_vector_insert(data->list, remote_name) < 0)
|
||||
return -1;
|
||||
}
|
||||
if (!strcmp(&name[namelen - 4], ".url"))
|
||||
remote_name = git__strndup(name, namelen - 4); /* strip ".url" */
|
||||
else
|
||||
remote_name = git__strndup(name, namelen - 8); /* strip ".pushurl" */
|
||||
GITERR_CHECK_ALLOC(remote_name);
|
||||
|
||||
return 0;
|
||||
return git_vector_insert(list, remote_name);
|
||||
}
|
||||
|
||||
int git_remote_list(git_strarray *remotes_list, git_repository *repo)
|
||||
{
|
||||
git_config *cfg;
|
||||
git_vector list;
|
||||
regex_t preg;
|
||||
struct cb_data data;
|
||||
int error;
|
||||
|
||||
if (git_repository_config__weakptr(&cfg, repo) < 0)
|
||||
@ -1112,18 +1103,13 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo)
|
||||
if (git_vector_init(&list, 4, git__strcmp_cb) < 0)
|
||||
return -1;
|
||||
|
||||
if (regcomp(&preg, "^remote\\.(.*)\\.(push)?url$", REG_EXTENDED) < 0) {
|
||||
giterr_set(GITERR_OS, "Remote catch regex failed to compile");
|
||||
return -1;
|
||||
}
|
||||
error = git_config_foreach_match(
|
||||
cfg, "^remote\\..*\\.(push)?url$", remote_list_cb, &list);
|
||||
|
||||
data.list = &list;
|
||||
data.preg = &preg;
|
||||
error = git_config_foreach(cfg, remote_list_cb, &data);
|
||||
regfree(&preg);
|
||||
if (error < 0) {
|
||||
size_t i;
|
||||
char *elem;
|
||||
|
||||
git_vector_foreach(&list, i, elem) {
|
||||
git__free(elem);
|
||||
}
|
||||
@ -1549,7 +1535,7 @@ int git_remote_add_push(git_remote *remote, const char *refspec)
|
||||
return add_refspec(remote, refspec, false);
|
||||
}
|
||||
|
||||
static int copy_refspecs(git_strarray *array, git_remote *remote, int push)
|
||||
static int copy_refspecs(git_strarray *array, git_remote *remote, unsigned int push)
|
||||
{
|
||||
size_t i;
|
||||
git_vector refspecs;
|
||||
|
||||
@ -33,8 +33,6 @@
|
||||
|
||||
#define GIT_REPO_VERSION 0
|
||||
|
||||
#define GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
|
||||
|
||||
static void set_odb(git_repository *repo, git_odb *odb)
|
||||
{
|
||||
if (odb) {
|
||||
@ -1134,31 +1132,34 @@ static int repo_init_structure(
|
||||
|
||||
/* Copy external template if requested */
|
||||
if (external_tpl) {
|
||||
git_config *cfg;
|
||||
const char *tdir;
|
||||
git_config *cfg = NULL;
|
||||
const char *tdir = NULL;
|
||||
bool default_template = false;
|
||||
git_buf template_buf = GIT_BUF_INIT;
|
||||
|
||||
if (opts->template_path)
|
||||
tdir = opts->template_path;
|
||||
else if ((error = git_config_open_default(&cfg)) < 0)
|
||||
return error;
|
||||
else {
|
||||
else if ((error = git_config_open_default(&cfg)) >= 0) {
|
||||
error = git_config_get_string(&tdir, cfg, "init.templatedir");
|
||||
|
||||
git_config_free(cfg);
|
||||
|
||||
if (error && error != GIT_ENOTFOUND)
|
||||
return error;
|
||||
|
||||
giterr_clear();
|
||||
tdir = GIT_TEMPLATE_DIR;
|
||||
}
|
||||
|
||||
error = git_futils_cp_r(tdir, repo_dir,
|
||||
GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_CHMOD_DIRS |
|
||||
GIT_CPDIR_SIMPLE_TO_MODE, dmode);
|
||||
if (!tdir) {
|
||||
if (!(error = git_futils_find_template_dir(&template_buf)))
|
||||
tdir = template_buf.ptr;
|
||||
default_template = true;
|
||||
}
|
||||
|
||||
if (tdir)
|
||||
error = git_futils_cp_r(tdir, repo_dir,
|
||||
GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_CHMOD_DIRS |
|
||||
GIT_CPDIR_SIMPLE_TO_MODE, dmode);
|
||||
|
||||
git_buf_free(&template_buf);
|
||||
git_config_free(cfg);
|
||||
|
||||
if (error < 0) {
|
||||
if (strcmp(tdir, GIT_TEMPLATE_DIR) != 0)
|
||||
if (!default_template)
|
||||
return error;
|
||||
|
||||
/* if template was default, ignore error and use internal */
|
||||
@ -1451,10 +1452,10 @@ int git_repository_head(git_reference **head_out, git_repository *repo)
|
||||
error = git_reference_lookup_resolved(head_out, repo, git_reference_symbolic_target(head), -1);
|
||||
git_reference_free(head);
|
||||
|
||||
return error == GIT_ENOTFOUND ? GIT_EORPHANEDHEAD : error;
|
||||
return error == GIT_ENOTFOUND ? GIT_EUNBORNBRANCH : error;
|
||||
}
|
||||
|
||||
int git_repository_head_orphan(git_repository *repo)
|
||||
int git_repository_head_unborn(git_repository *repo)
|
||||
{
|
||||
git_reference *ref = NULL;
|
||||
int error;
|
||||
@ -1462,7 +1463,7 @@ int git_repository_head_orphan(git_repository *repo)
|
||||
error = git_repository_head(&ref, repo);
|
||||
git_reference_free(ref);
|
||||
|
||||
if (error == GIT_EORPHANEDHEAD)
|
||||
if (error == GIT_EUNBORNBRANCH)
|
||||
return 1;
|
||||
|
||||
if (error < 0)
|
||||
@ -1649,7 +1650,7 @@ int git_repository_hashfile(
|
||||
const char *as_path)
|
||||
{
|
||||
int error;
|
||||
git_vector filters = GIT_VECTOR_INIT;
|
||||
git_filter_list *fl = NULL;
|
||||
git_file fd = -1;
|
||||
git_off_t len;
|
||||
git_buf full_path = GIT_BUF_INIT;
|
||||
@ -1671,7 +1672,8 @@ int git_repository_hashfile(
|
||||
|
||||
/* passing empty string for "as_path" indicated --no-filters */
|
||||
if (strlen(as_path) > 0) {
|
||||
error = git_filters_load(&filters, repo, as_path, GIT_FILTER_TO_ODB);
|
||||
error = git_filter_list_load(
|
||||
&fl, repo, NULL, as_path, GIT_FILTER_TO_ODB);
|
||||
if (error < 0)
|
||||
return error;
|
||||
} else {
|
||||
@ -1698,12 +1700,12 @@ int git_repository_hashfile(
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, &filters);
|
||||
error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, fl);
|
||||
|
||||
cleanup:
|
||||
if (fd >= 0)
|
||||
p_close(fd);
|
||||
git_filters_free(&filters);
|
||||
git_filter_list_free(fl);
|
||||
git_buf_free(&full_path);
|
||||
|
||||
return error;
|
||||
|
||||
@ -14,8 +14,6 @@
|
||||
#include "git2/revparse.h"
|
||||
#include "merge.h"
|
||||
|
||||
#include <regex.h>
|
||||
|
||||
git_commit_list_node *git_revwalk__commit_lookup(
|
||||
git_revwalk *walk, const git_oid *oid)
|
||||
{
|
||||
@ -181,48 +179,35 @@ static int push_glob_cb(const char *refname, void *data_)
|
||||
|
||||
static int push_glob(git_revwalk *walk, const char *glob, int hide)
|
||||
{
|
||||
int error = 0;
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
struct push_cb_data data;
|
||||
regex_t preg;
|
||||
size_t wildcard;
|
||||
|
||||
assert(walk && glob);
|
||||
|
||||
/* refs/ is implied if not given in the glob */
|
||||
if (strncmp(glob, GIT_REFS_DIR, strlen(GIT_REFS_DIR))) {
|
||||
git_buf_printf(&buf, GIT_REFS_DIR "%s", glob);
|
||||
} else {
|
||||
if (git__prefixcmp(glob, GIT_REFS_DIR) != 0)
|
||||
git_buf_joinpath(&buf, GIT_REFS_DIR, glob);
|
||||
else
|
||||
git_buf_puts(&buf, glob);
|
||||
}
|
||||
|
||||
/* If no '?', '*' or '[' exist, we append '/ *' to the glob */
|
||||
memset(&preg, 0x0, sizeof(regex_t));
|
||||
if (regcomp(&preg, "[?*[]", REG_EXTENDED)) {
|
||||
giterr_set(GITERR_OS, "Regex failed to compile");
|
||||
git_buf_free(&buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (regexec(&preg, glob, 0, NULL, 0))
|
||||
git_buf_puts(&buf, "/*");
|
||||
|
||||
if (git_buf_oom(&buf))
|
||||
goto on_error;
|
||||
wildcard = strcspn(glob, "?*[");
|
||||
if (!glob[wildcard])
|
||||
git_buf_put(&buf, "/*", 2);
|
||||
|
||||
data.walk = walk;
|
||||
data.hide = hide;
|
||||
|
||||
if (git_reference_foreach_glob(
|
||||
walk->repo, git_buf_cstr(&buf), push_glob_cb, &data) < 0)
|
||||
goto on_error;
|
||||
if (git_buf_oom(&buf))
|
||||
error = -1;
|
||||
else
|
||||
error = git_reference_foreach_glob(
|
||||
walk->repo, git_buf_cstr(&buf), push_glob_cb, &data);
|
||||
|
||||
regfree(&preg);
|
||||
git_buf_free(&buf);
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
regfree(&preg);
|
||||
git_buf_free(&buf);
|
||||
return -1;
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_revwalk_push_glob(git_revwalk *walk, const char *glob)
|
||||
|
||||
@ -27,7 +27,7 @@ static int retrieve_head(git_reference **out, git_repository *repo)
|
||||
{
|
||||
int error = git_repository_head(out, repo);
|
||||
|
||||
if (error == GIT_EORPHANEDHEAD)
|
||||
if (error == GIT_EUNBORNBRANCH)
|
||||
return create_error(error, "You do not have the initial commit yet.");
|
||||
|
||||
return error;
|
||||
|
||||
@ -252,7 +252,7 @@ int git_status_list_new(
|
||||
|
||||
/* if there is no HEAD, that's okay - we'll make an empty iterator */
|
||||
if (((error = git_repository_head_tree(&head, repo)) < 0) &&
|
||||
error != GIT_ENOTFOUND && error != GIT_EORPHANEDHEAD) {
|
||||
error != GIT_ENOTFOUND && error != GIT_EUNBORNBRANCH) {
|
||||
git_index_free(index); /* release index */
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -1557,7 +1557,7 @@ static void submodule_get_wd_status(
|
||||
if (ign == GIT_SUBMODULE_IGNORE_NONE)
|
||||
opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
|
||||
|
||||
/* if we don't have an orphaned head, check diff with index */
|
||||
/* if we don't have an unborn head, check diff with index */
|
||||
if (git_repository_head_tree(&sm_head, sm_repo) < 0)
|
||||
giterr_clear();
|
||||
else {
|
||||
|
||||
@ -59,7 +59,7 @@ typedef struct {
|
||||
git_smart_subtransport parent;
|
||||
transport_smart *owner;
|
||||
gitno_socket socket;
|
||||
const char *path;
|
||||
char *path;
|
||||
char *host;
|
||||
char *port;
|
||||
char *user_from_url;
|
||||
@ -125,15 +125,9 @@ static int gen_request(
|
||||
size_t content_length)
|
||||
{
|
||||
http_subtransport *t = OWNING_SUBTRANSPORT(s);
|
||||
const char *path = t->path ? t->path : "/";
|
||||
|
||||
if (!t->path)
|
||||
t->path = "/";
|
||||
|
||||
/* If we were redirected, make sure to respect that here */
|
||||
if (s->redirect_url)
|
||||
git_buf_printf(buf, "%s %s HTTP/1.1\r\n", s->verb, s->redirect_url);
|
||||
else
|
||||
git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, t->path, s->service_url);
|
||||
git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url);
|
||||
|
||||
git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
|
||||
git_buf_printf(buf, "Host: %s\r\n", t->host);
|
||||
@ -209,7 +203,7 @@ static int on_header_ready(http_subtransport *t)
|
||||
}
|
||||
else if (!strcasecmp("Location", git_buf_cstr(name))) {
|
||||
if (!t->location) {
|
||||
t->location= git__strdup(git_buf_cstr(value));
|
||||
t->location = git__strdup(git_buf_cstr(value));
|
||||
GITERR_CHECK_ALLOC(t->location);
|
||||
}
|
||||
}
|
||||
@ -255,6 +249,98 @@ static int on_header_value(http_parser *parser, const char *str, size_t len)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void free_connection_data(http_subtransport *t)
|
||||
{
|
||||
if (t->host) {
|
||||
git__free(t->host);
|
||||
t->host = NULL;
|
||||
}
|
||||
|
||||
if (t->port) {
|
||||
git__free(t->port);
|
||||
t->port = NULL;
|
||||
}
|
||||
|
||||
if (t->user_from_url) {
|
||||
git__free(t->user_from_url);
|
||||
t->user_from_url = NULL;
|
||||
}
|
||||
|
||||
if (t->pass_from_url) {
|
||||
git__free(t->pass_from_url);
|
||||
t->pass_from_url = NULL;
|
||||
}
|
||||
|
||||
if (t->path) {
|
||||
git__free(t->path);
|
||||
t->path = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int set_connection_data_from_url(
|
||||
http_subtransport *t, const char *url, const char *service_suffix)
|
||||
{
|
||||
int error = 0;
|
||||
const char *default_port = NULL;
|
||||
char *original_host = NULL;
|
||||
|
||||
if (!git__prefixcmp(url, prefix_http)) {
|
||||
url = url + strlen(prefix_http);
|
||||
default_port = "80";
|
||||
|
||||
if (t->use_ssl) {
|
||||
giterr_set(GITERR_NET, "Redirect from HTTPS to HTTP not allowed");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!git__prefixcmp(url, prefix_https)) {
|
||||
url += strlen(prefix_https);
|
||||
default_port = "443";
|
||||
t->use_ssl = 1;
|
||||
}
|
||||
|
||||
if (!default_port) {
|
||||
giterr_set(GITERR_NET, "Unrecognized URL prefix");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* preserve original host name for checking */
|
||||
original_host = t->host;
|
||||
t->host = NULL;
|
||||
|
||||
free_connection_data(t);
|
||||
|
||||
error = gitno_extract_url_parts(
|
||||
&t->host, &t->port, &t->user_from_url, &t->pass_from_url,
|
||||
url, default_port);
|
||||
|
||||
if (!error) {
|
||||
const char *path = strchr(url, '/');
|
||||
size_t pathlen = strlen(path);
|
||||
size_t suffixlen = service_suffix ? strlen(service_suffix) : 0;
|
||||
|
||||
if (suffixlen &&
|
||||
!memcmp(path + pathlen - suffixlen, service_suffix, suffixlen))
|
||||
t->path = git__strndup(path, pathlen - suffixlen);
|
||||
else
|
||||
t->path = git__strdup(path);
|
||||
|
||||
/* Allow '/'-led urls, or a change of protocol */
|
||||
if (original_host != NULL) {
|
||||
if (strcmp(original_host, t->host) && t->location[0] != '/') {
|
||||
giterr_set(GITERR_NET, "Cross host redirect not allowed");
|
||||
error = -1;
|
||||
}
|
||||
|
||||
git__free(original_host);
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
static int on_headers_complete(http_parser *parser)
|
||||
{
|
||||
parser_context *ctx = (parser_context *) parser->data;
|
||||
@ -308,10 +394,8 @@ static int on_headers_complete(http_parser *parser)
|
||||
return t->parse_error = PARSE_ERROR_GENERIC;
|
||||
}
|
||||
|
||||
if (t->location[0] != '/') {
|
||||
giterr_set(GITERR_NET, "Only relative redirects are supported");
|
||||
if (set_connection_data_from_url(t, t->location, s->service_url) < 0)
|
||||
return t->parse_error = PARSE_ERROR_GENERIC;
|
||||
}
|
||||
|
||||
/* Set the redirect URL on the stream. This is a transfer of
|
||||
* ownership of the memory. */
|
||||
@ -822,50 +906,31 @@ static int http_action(
|
||||
git_smart_service_t action)
|
||||
{
|
||||
http_subtransport *t = (http_subtransport *)subtransport;
|
||||
const char *default_port = NULL;
|
||||
int ret;
|
||||
|
||||
if (!stream)
|
||||
return -1;
|
||||
|
||||
if (!t->host || !t->port || !t->path) {
|
||||
if (!git__prefixcmp(url, prefix_http)) {
|
||||
url = url + strlen(prefix_http);
|
||||
default_port = "80";
|
||||
}
|
||||
|
||||
if (!git__prefixcmp(url, prefix_https)) {
|
||||
url += strlen(prefix_https);
|
||||
default_port = "443";
|
||||
t->use_ssl = 1;
|
||||
}
|
||||
|
||||
if (!default_port)
|
||||
return -1;
|
||||
|
||||
if ((ret = gitno_extract_url_parts(&t->host, &t->port,
|
||||
&t->user_from_url, &t->pass_from_url, url, default_port)) < 0)
|
||||
if ((ret = set_connection_data_from_url(t, url, NULL)) < 0)
|
||||
return ret;
|
||||
|
||||
t->path = strchr(url, '/');
|
||||
}
|
||||
|
||||
if (http_connect(t) < 0)
|
||||
return -1;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case GIT_SERVICE_UPLOADPACK_LS:
|
||||
return http_uploadpack_ls(t, stream);
|
||||
switch (action) {
|
||||
case GIT_SERVICE_UPLOADPACK_LS:
|
||||
return http_uploadpack_ls(t, stream);
|
||||
|
||||
case GIT_SERVICE_UPLOADPACK:
|
||||
return http_uploadpack(t, stream);
|
||||
case GIT_SERVICE_UPLOADPACK:
|
||||
return http_uploadpack(t, stream);
|
||||
|
||||
case GIT_SERVICE_RECEIVEPACK_LS:
|
||||
return http_receivepack_ls(t, stream);
|
||||
case GIT_SERVICE_RECEIVEPACK_LS:
|
||||
return http_receivepack_ls(t, stream);
|
||||
|
||||
case GIT_SERVICE_RECEIVEPACK:
|
||||
return http_receivepack(t, stream);
|
||||
case GIT_SERVICE_RECEIVEPACK:
|
||||
return http_receivepack(t, stream);
|
||||
}
|
||||
|
||||
*stream = NULL;
|
||||
@ -893,25 +958,7 @@ static int http_close(git_smart_subtransport *subtransport)
|
||||
t->url_cred = NULL;
|
||||
}
|
||||
|
||||
if (t->host) {
|
||||
git__free(t->host);
|
||||
t->host = NULL;
|
||||
}
|
||||
|
||||
if (t->port) {
|
||||
git__free(t->port);
|
||||
t->port = NULL;
|
||||
}
|
||||
|
||||
if (t->user_from_url) {
|
||||
git__free(t->user_from_url);
|
||||
t->user_from_url = NULL;
|
||||
}
|
||||
|
||||
if (t->pass_from_url) {
|
||||
git__free(t->pass_from_url);
|
||||
t->pass_from_url = NULL;
|
||||
}
|
||||
free_connection_data(t);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -37,6 +37,14 @@ typedef struct {
|
||||
git_cred *cred;
|
||||
} ssh_subtransport;
|
||||
|
||||
static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg)
|
||||
{
|
||||
char *ssherr;
|
||||
libssh2_session_last_error(session, &ssherr, NULL, 0);
|
||||
|
||||
giterr_set(GITERR_SSH, "%s: %s", errmsg, ssherr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a git protocol request.
|
||||
*
|
||||
@ -81,8 +89,8 @@ static int send_command(ssh_stream *s)
|
||||
goto cleanup;
|
||||
|
||||
error = libssh2_channel_exec(s->channel, request.ptr);
|
||||
if (error < 0) {
|
||||
giterr_set(GITERR_NET, "SSH could not execute request");
|
||||
if (error < LIBSSH2_ERROR_NONE) {
|
||||
ssh_error(s->session, "SSH could not execute request");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@ -107,8 +115,8 @@ static int ssh_stream_read(
|
||||
if (!s->sent_command && send_command(s) < 0)
|
||||
return -1;
|
||||
|
||||
if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < 0) {
|
||||
giterr_set(GITERR_NET, "SSH could not read data");
|
||||
if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) {
|
||||
ssh_error(s->session, "SSH could not read data");;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -127,8 +135,8 @@ static int ssh_stream_write(
|
||||
if (!s->sent_command && send_command(s) < 0)
|
||||
return -1;
|
||||
|
||||
if (libssh2_channel_write(s->channel, buffer, len) < 0) {
|
||||
giterr_set(GITERR_NET, "SSH could not write data");
|
||||
if (libssh2_channel_write(s->channel, buffer, len) < LIBSSH2_ERROR_NONE) {
|
||||
ssh_error(s->session, "SSH could not write data");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -212,7 +220,7 @@ static int git_ssh_extract_url_parts(
|
||||
|
||||
at = strchr(url, '@');
|
||||
if (at) {
|
||||
start = at+1;
|
||||
start = at + 1;
|
||||
*username = git__substrdup(url, at - url);
|
||||
GITERR_CHECK_ALLOC(*username);
|
||||
} else {
|
||||
@ -262,8 +270,8 @@ static int _git_ssh_authenticate_session(
|
||||
}
|
||||
} while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
|
||||
|
||||
if (rc != 0) {
|
||||
giterr_set(GITERR_NET, "Failed to authenticate SSH session");
|
||||
if (rc != LIBSSH2_ERROR_NONE) {
|
||||
ssh_error(session, "Failed to authenticate SSH session");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -289,9 +297,9 @@ static int _git_ssh_session_create(
|
||||
rc = libssh2_session_startup(s, socket.socket);
|
||||
} while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
|
||||
|
||||
if (0 != rc) {
|
||||
if (rc != LIBSSH2_ERROR_NONE) {
|
||||
ssh_error(s, "Failed to start SSH session");
|
||||
libssh2_session_free(s);
|
||||
giterr_set(GITERR_NET, "Failed to start SSH session");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -346,11 +354,11 @@ static int _git_ssh_setup_conn(
|
||||
goto on_error;
|
||||
|
||||
if (!t->cred) {
|
||||
giterr_set(GITERR_NET, "Callback failed to initialize SSH credentials");
|
||||
giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials");
|
||||
goto on_error;
|
||||
}
|
||||
} else {
|
||||
giterr_set(GITERR_NET, "Cannot set up SSH connection without credentials");
|
||||
giterr_set(GITERR_SSH, "Cannot set up SSH connection without credentials");
|
||||
goto on_error;
|
||||
}
|
||||
assert(t->cred);
|
||||
@ -368,7 +376,7 @@ static int _git_ssh_setup_conn(
|
||||
|
||||
channel = libssh2_channel_open_session(session);
|
||||
if (!channel) {
|
||||
giterr_set(GITERR_NET, "Failed to open SSH channel");
|
||||
ssh_error(session, "Failed to open SSH channel");
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
git_smart_subtransport parent;
|
||||
transport_smart *owner;
|
||||
const char *path;
|
||||
char *path;
|
||||
char *host;
|
||||
char *port;
|
||||
char *user_from_url;
|
||||
@ -152,6 +152,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
|
||||
wchar_t *types[] = { L"*/*", NULL };
|
||||
BOOL peerdist = FALSE;
|
||||
int error = -1, wide_len;
|
||||
unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS;
|
||||
|
||||
/* Prepare URL */
|
||||
git_buf_printf(&buf, "%s%s", t->path, s->service_url);
|
||||
@ -195,7 +196,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
|
||||
}
|
||||
|
||||
/* Set proxy if necessary */
|
||||
if (git_remote__get_http_proxy(t->owner->owner, t->use_ssl, &proxy_url) < 0)
|
||||
if (git_remote__get_http_proxy(t->owner->owner, !!t->use_ssl, &proxy_url) < 0)
|
||||
goto on_error;
|
||||
|
||||
if (proxy_url) {
|
||||
@ -244,6 +245,17 @@ static int winhttp_stream_connect(winhttp_stream *s)
|
||||
git__free(proxy_wide);
|
||||
}
|
||||
|
||||
/* Disable WinHTTP redirects so we can handle them manually. Why, you ask?
|
||||
* http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/b2ff8879-ab9f-4218-8f09-16d25dff87ae
|
||||
*/
|
||||
if (!WinHttpSetOption(s->request,
|
||||
WINHTTP_OPTION_DISABLE_FEATURE,
|
||||
&disable_redirects,
|
||||
sizeof(disable_redirects))) {
|
||||
giterr_set(GITERR_OS, "Failed to disable redirects");
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
/* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP
|
||||
* adds itself. This option may not be supported by the underlying
|
||||
* platform, so we do not error-check it */
|
||||
@ -380,6 +392,142 @@ static int write_chunk(HINTERNET request, const char *buffer, size_t len)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void free_connection_data(winhttp_subtransport *t)
|
||||
{
|
||||
if (t->host) {
|
||||
git__free(t->host);
|
||||
t->host = NULL;
|
||||
}
|
||||
|
||||
if (t->port) {
|
||||
git__free(t->port);
|
||||
t->port = NULL;
|
||||
}
|
||||
|
||||
if (t->user_from_url) {
|
||||
git__free(t->user_from_url);
|
||||
t->user_from_url = NULL;
|
||||
}
|
||||
|
||||
if (t->pass_from_url) {
|
||||
git__free(t->pass_from_url);
|
||||
t->pass_from_url = NULL;
|
||||
}
|
||||
|
||||
if (t->path) {
|
||||
git__free(t->path);
|
||||
t->path = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int set_connection_data_from_url(
|
||||
winhttp_subtransport *t, const char *url, const char *service_suffix)
|
||||
{
|
||||
int error = 0;
|
||||
const char *default_port = NULL;
|
||||
char *original_host = NULL;
|
||||
const char *original_url = url;
|
||||
|
||||
if (!git__prefixcmp(url, prefix_http)) {
|
||||
url += strlen(prefix_http);
|
||||
default_port = "80";
|
||||
|
||||
if (t->use_ssl) {
|
||||
giterr_set(GITERR_NET, "Redirect from HTTPS to HTTP not allowed");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!git__prefixcmp(url, prefix_https)) {
|
||||
url += strlen(prefix_https);
|
||||
default_port = "443";
|
||||
t->use_ssl = 1;
|
||||
}
|
||||
|
||||
if (!default_port) {
|
||||
giterr_set(GITERR_NET, "Unrecognized URL prefix");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* preserve original host name for checking */
|
||||
original_host = t->host;
|
||||
t->host = NULL;
|
||||
|
||||
free_connection_data(t);
|
||||
|
||||
error = gitno_extract_url_parts(
|
||||
&t->host, &t->port, &t->user_from_url, &t->pass_from_url,
|
||||
url, default_port);
|
||||
|
||||
if (!error) {
|
||||
const char *path = strchr(url, '/');
|
||||
size_t pathlen = strlen(path);
|
||||
size_t suffixlen = service_suffix ? strlen(service_suffix) : 0;
|
||||
|
||||
if (suffixlen &&
|
||||
!memcmp(path + pathlen - suffixlen, service_suffix, suffixlen))
|
||||
t->path = git__strndup(path, pathlen - suffixlen);
|
||||
else
|
||||
t->path = git__strdup(path);
|
||||
|
||||
/* Allow '/'-led urls, or a change of protocol */
|
||||
if (original_host != NULL) {
|
||||
if (strcmp(original_host, t->host) && original_url[0] != '/') {
|
||||
giterr_set(GITERR_NET, "Cross host redirect not allowed");
|
||||
error = -1;
|
||||
}
|
||||
|
||||
git__free(original_host);
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int winhttp_connect(
|
||||
winhttp_subtransport *t,
|
||||
const char *url)
|
||||
{
|
||||
wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";
|
||||
git_win32_path host;
|
||||
int32_t port;
|
||||
const char *default_port = "80";
|
||||
|
||||
/* Prepare port */
|
||||
if (git__strtol32(&port, t->port, NULL, 10) < 0)
|
||||
return -1;
|
||||
|
||||
/* Prepare host */
|
||||
git_win32_path_from_c(host, t->host);
|
||||
|
||||
/* Establish session */
|
||||
t->session = WinHttpOpen(
|
||||
ua,
|
||||
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME,
|
||||
WINHTTP_NO_PROXY_BYPASS,
|
||||
0);
|
||||
|
||||
if (!t->session) {
|
||||
giterr_set(GITERR_OS, "Failed to init WinHTTP");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Establish connection */
|
||||
t->connection = WinHttpConnect(
|
||||
t->session,
|
||||
host,
|
||||
(INTERNET_PORT) port,
|
||||
0);
|
||||
|
||||
if (!t->connection) {
|
||||
giterr_set(GITERR_OS, "Failed to connect to host");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int winhttp_stream_read(
|
||||
git_smart_subtransport_stream *stream,
|
||||
char *buffer,
|
||||
@ -511,50 +659,52 @@ replay:
|
||||
|
||||
/* Check for Windows 7. This workaround is only necessary on
|
||||
* Windows Vista and earlier. Windows 7 is version 6.1. */
|
||||
if (!git_has_win32_version(6, 1, 0)) {
|
||||
wchar_t *location;
|
||||
DWORD location_length;
|
||||
int redirect_cmp;
|
||||
wchar_t *location;
|
||||
DWORD location_length;
|
||||
char *location8;
|
||||
|
||||
/* OK, fetch the Location header from the redirect. */
|
||||
if (WinHttpQueryHeaders(s->request,
|
||||
WINHTTP_QUERY_LOCATION,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX,
|
||||
WINHTTP_NO_OUTPUT_BUFFER,
|
||||
&location_length,
|
||||
WINHTTP_NO_HEADER_INDEX) ||
|
||||
GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
||||
giterr_set(GITERR_OS, "Failed to read Location header");
|
||||
return -1;
|
||||
}
|
||||
|
||||
location = git__malloc(location_length);
|
||||
GITERR_CHECK_ALLOC(location);
|
||||
|
||||
if (!WinHttpQueryHeaders(s->request,
|
||||
WINHTTP_QUERY_LOCATION,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX,
|
||||
location,
|
||||
&location_length,
|
||||
WINHTTP_NO_HEADER_INDEX)) {
|
||||
giterr_set(GITERR_OS, "Failed to read Location header");
|
||||
git__free(location);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Compare the Location header with the request URI */
|
||||
redirect_cmp = wcscmp(location, s->request_uri);
|
||||
git__free(location);
|
||||
|
||||
if (!redirect_cmp) {
|
||||
/* Replay the request */
|
||||
WinHttpCloseHandle(s->request);
|
||||
s->request = NULL;
|
||||
s->sent_request = 0;
|
||||
|
||||
goto replay;
|
||||
}
|
||||
/* OK, fetch the Location header from the redirect. */
|
||||
if (WinHttpQueryHeaders(s->request,
|
||||
WINHTTP_QUERY_LOCATION,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX,
|
||||
WINHTTP_NO_OUTPUT_BUFFER,
|
||||
&location_length,
|
||||
WINHTTP_NO_HEADER_INDEX) ||
|
||||
GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
||||
giterr_set(GITERR_OS, "Failed to read Location header");
|
||||
return -1;
|
||||
}
|
||||
|
||||
location = git__malloc(location_length);
|
||||
location8 = git__malloc(location_length);
|
||||
GITERR_CHECK_ALLOC(location);
|
||||
|
||||
if (!WinHttpQueryHeaders(s->request,
|
||||
WINHTTP_QUERY_LOCATION,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX,
|
||||
location,
|
||||
&location_length,
|
||||
WINHTTP_NO_HEADER_INDEX)) {
|
||||
giterr_set(GITERR_OS, "Failed to read Location header");
|
||||
git__free(location);
|
||||
return -1;
|
||||
}
|
||||
git__utf16_to_8(location8, location_length, location);
|
||||
git__free(location);
|
||||
|
||||
/* Replay the request */
|
||||
WinHttpCloseHandle(s->request);
|
||||
s->request = NULL;
|
||||
s->sent_request = 0;
|
||||
|
||||
if (!git__prefixcmp_icase(location8, prefix_https)) {
|
||||
/* Upgrade to secure connection; disconnect and start over */
|
||||
set_connection_data_from_url(t, location8, s->service_url);
|
||||
winhttp_connect(t, location8);
|
||||
}
|
||||
|
||||
git__free(location8);
|
||||
goto replay;
|
||||
}
|
||||
|
||||
/* Handle authentication failures */
|
||||
@ -888,68 +1038,6 @@ static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int winhttp_connect(
|
||||
winhttp_subtransport *t,
|
||||
const char *url)
|
||||
{
|
||||
wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";
|
||||
git_win32_path host;
|
||||
int32_t port;
|
||||
const char *default_port = "80";
|
||||
int ret;
|
||||
|
||||
if (!git__prefixcmp(url, prefix_http)) {
|
||||
url = url + strlen(prefix_http);
|
||||
default_port = "80";
|
||||
}
|
||||
|
||||
if (!git__prefixcmp(url, prefix_https)) {
|
||||
url += strlen(prefix_https);
|
||||
default_port = "443";
|
||||
t->use_ssl = 1;
|
||||
}
|
||||
|
||||
if ((ret = gitno_extract_url_parts(&t->host, &t->port, &t->user_from_url,
|
||||
&t->pass_from_url, url, default_port)) < 0)
|
||||
return ret;
|
||||
|
||||
t->path = strchr(url, '/');
|
||||
|
||||
/* Prepare port */
|
||||
if (git__strtol32(&port, t->port, NULL, 10) < 0)
|
||||
return -1;
|
||||
|
||||
/* Prepare host */
|
||||
git_win32_path_from_c(host, t->host);
|
||||
|
||||
/* Establish session */
|
||||
t->session = WinHttpOpen(
|
||||
ua,
|
||||
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME,
|
||||
WINHTTP_NO_PROXY_BYPASS,
|
||||
0);
|
||||
|
||||
if (!t->session) {
|
||||
giterr_set(GITERR_OS, "Failed to init WinHTTP");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Establish connection */
|
||||
t->connection = WinHttpConnect(
|
||||
t->session,
|
||||
host,
|
||||
port,
|
||||
0);
|
||||
|
||||
if (!t->connection) {
|
||||
giterr_set(GITERR_OS, "Failed to connect to host");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int winhttp_uploadpack_ls(
|
||||
winhttp_subtransport *t,
|
||||
winhttp_stream *s)
|
||||
@ -1014,7 +1102,8 @@ static int winhttp_action(
|
||||
int ret = -1;
|
||||
|
||||
if (!t->connection &&
|
||||
winhttp_connect(t, url) < 0)
|
||||
(set_connection_data_from_url(t, url, NULL) < 0 ||
|
||||
winhttp_connect(t, url) < 0))
|
||||
return -1;
|
||||
|
||||
if (winhttp_stream_alloc(t, &s) < 0)
|
||||
@ -1056,25 +1145,7 @@ static int winhttp_close(git_smart_subtransport *subtransport)
|
||||
winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
|
||||
int ret = 0;
|
||||
|
||||
if (t->host) {
|
||||
git__free(t->host);
|
||||
t->host = NULL;
|
||||
}
|
||||
|
||||
if (t->port) {
|
||||
git__free(t->port);
|
||||
t->port = NULL;
|
||||
}
|
||||
|
||||
if (t->user_from_url) {
|
||||
git__free(t->user_from_url);
|
||||
t->user_from_url = NULL;
|
||||
}
|
||||
|
||||
if (t->pass_from_url) {
|
||||
git__free(t->pass_from_url);
|
||||
t->pass_from_url = NULL;
|
||||
}
|
||||
free_connection_data(t);
|
||||
|
||||
if (t->cred) {
|
||||
t->cred->free(t->cred);
|
||||
|
||||
16
src/util.c
16
src/util.c
@ -117,6 +117,19 @@ int git_libgit2_opts(int key, ...)
|
||||
*(va_arg(ap, ssize_t *)) = git_cache__current_storage.val;
|
||||
*(va_arg(ap, ssize_t *)) = git_cache__max_storage;
|
||||
break;
|
||||
|
||||
case GIT_OPT_GET_TEMPLATE_PATH:
|
||||
{
|
||||
char *out = va_arg(ap, char *);
|
||||
size_t outlen = va_arg(ap, size_t);
|
||||
|
||||
error = git_futils_dirs_get_str(out, outlen, GIT_FUTILS_DIR_TEMPLATE);
|
||||
}
|
||||
break;
|
||||
|
||||
case GIT_OPT_SET_TEMPLATE_PATH:
|
||||
error = git_futils_dirs_set(GIT_FUTILS_DIR_TEMPLATE, va_arg(ap, const char *));
|
||||
break;
|
||||
}
|
||||
|
||||
va_end(ap);
|
||||
@ -679,6 +692,9 @@ size_t git__unescape(char *str)
|
||||
{
|
||||
char *scan, *pos = str;
|
||||
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
for (scan = str; *scan; pos++, scan++) {
|
||||
if (*scan == '\\' && *(scan + 1) != '\0')
|
||||
scan++; /* skip '\' but include next char */
|
||||
|
||||
@ -86,7 +86,7 @@ static wchar_t* win32_walkpath(wchar_t *path, wchar_t *buf, size_t buflen)
|
||||
return (path != base) ? path : NULL;
|
||||
}
|
||||
|
||||
static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe)
|
||||
static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe, const wchar_t *subdir)
|
||||
{
|
||||
wchar_t *env = _wgetenv(L"PATH"), lastch;
|
||||
struct git_win32__path root;
|
||||
@ -110,8 +110,8 @@ static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe)
|
||||
wcscpy(&root.path[root.len], gitexe);
|
||||
|
||||
if (_waccess(root.path, F_OK) == 0 && root.len > 5) {
|
||||
/* replace "bin\\" or "cmd\\" with "etc\\" */
|
||||
wcscpy(&root.path[root.len - 4], L"etc\\");
|
||||
/* replace "bin\\" or "cmd\\" with subdir */
|
||||
wcscpy(&root.path[root.len - 4], subdir);
|
||||
|
||||
win32_path_to_8(buf, root.path);
|
||||
return 0;
|
||||
@ -122,7 +122,7 @@ static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe)
|
||||
}
|
||||
|
||||
static int win32_find_git_in_registry(
|
||||
git_buf *buf, const HKEY hieve, const wchar_t *key)
|
||||
git_buf *buf, const HKEY hieve, const wchar_t *key, const wchar_t *subdir)
|
||||
{
|
||||
HKEY hKey;
|
||||
DWORD dwType = REG_SZ;
|
||||
@ -130,9 +130,9 @@ static int win32_find_git_in_registry(
|
||||
|
||||
assert(buf);
|
||||
|
||||
path16.len = 0;
|
||||
path16.len = MAX_PATH;
|
||||
|
||||
if (RegOpenKeyExW(hieve, key, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) {
|
||||
if (RegOpenKeyExW(hieve, key, 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
|
||||
if (RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType,
|
||||
(LPBYTE)&path16.path, &path16.len) == ERROR_SUCCESS)
|
||||
{
|
||||
@ -143,7 +143,7 @@ static int win32_find_git_in_registry(
|
||||
return -1;
|
||||
}
|
||||
|
||||
wcscat(path16.path, L"etc\\");
|
||||
wcscat(path16.path, subdir);
|
||||
path16.len += 4;
|
||||
|
||||
win32_path_to_8(buf, path16.path);
|
||||
@ -180,26 +180,26 @@ static int win32_find_existing_dirs(
|
||||
return (git_buf_oom(out) ? -1 : 0);
|
||||
}
|
||||
|
||||
int git_win32__find_system_dirs(git_buf *out)
|
||||
int git_win32__find_system_dirs(git_buf *out, const wchar_t *subdir)
|
||||
{
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
|
||||
/* directories where git.exe & git.cmd are found */
|
||||
if (!win32_find_git_in_path(&buf, L"git.exe") && buf.size)
|
||||
if (!win32_find_git_in_path(&buf, L"git.exe", subdir) && buf.size)
|
||||
git_buf_set(out, buf.ptr, buf.size);
|
||||
else
|
||||
git_buf_clear(out);
|
||||
|
||||
if (!win32_find_git_in_path(&buf, L"git.cmd") && buf.size)
|
||||
if (!win32_find_git_in_path(&buf, L"git.cmd", subdir) && buf.size)
|
||||
git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
|
||||
|
||||
/* directories where git is installed according to registry */
|
||||
if (!win32_find_git_in_registry(
|
||||
&buf, HKEY_CURRENT_USER, REG_MSYSGIT_INSTALL_LOCAL) && buf.size)
|
||||
&buf, HKEY_CURRENT_USER, REG_MSYSGIT_INSTALL_LOCAL, subdir) && buf.size)
|
||||
git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
|
||||
|
||||
if (!win32_find_git_in_registry(
|
||||
&buf, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL) && buf.size)
|
||||
&buf, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL, subdir) && buf.size)
|
||||
git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
|
||||
|
||||
git_buf_free(&buf);
|
||||
|
||||
@ -19,7 +19,7 @@ extern int git_win32__expand_path(
|
||||
extern int git_win32__find_file(
|
||||
git_buf *path, const struct git_win32__path *root, const char *filename);
|
||||
|
||||
extern int git_win32__find_system_dirs(git_buf *out);
|
||||
extern int git_win32__find_system_dirs(git_buf *out, const wchar_t *subpath);
|
||||
extern int git_win32__find_global_dirs(git_buf *out);
|
||||
extern int git_win32__find_xdg_dirs(git_buf *out);
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "pthread.h"
|
||||
#include "../global.h"
|
||||
|
||||
int pthread_create(
|
||||
pthread_t *GIT_RESTRICT thread,
|
||||
@ -217,6 +218,14 @@ int pthread_rwlock_destroy(pthread_rwlock_t *lock)
|
||||
}
|
||||
|
||||
|
||||
static void win32_pthread_shutdown(void)
|
||||
{
|
||||
if (win32_kernel32_dll) {
|
||||
FreeLibrary(win32_kernel32_dll);
|
||||
win32_kernel32_dll = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int win32_pthread_initialize(void)
|
||||
{
|
||||
if (win32_kernel32_dll)
|
||||
@ -239,15 +248,7 @@ int win32_pthread_initialize(void)
|
||||
win32_srwlock_release_exclusive = (win32_srwlock_fn)
|
||||
GetProcAddress(win32_kernel32_dll, "ReleaseSRWLockExclusive");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int win32_pthread_shutdown(void)
|
||||
{
|
||||
if (win32_kernel32_dll) {
|
||||
FreeLibrary(win32_kernel32_dll);
|
||||
win32_kernel32_dll = NULL;
|
||||
}
|
||||
git__on_shutdown(win32_pthread_shutdown);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -69,6 +69,5 @@ int pthread_rwlock_wrunlock(pthread_rwlock_t *);
|
||||
int pthread_rwlock_destroy(pthread_rwlock_t *);
|
||||
|
||||
extern int win32_pthread_initialize(void);
|
||||
extern int win32_pthread_shutdown(void);
|
||||
|
||||
#endif
|
||||
|
||||
@ -100,6 +100,22 @@ void test_attr_repo__get_many(void)
|
||||
cl_assert_equal_s("yes", values[3]);
|
||||
}
|
||||
|
||||
void test_attr_repo__get_many_in_place(void)
|
||||
{
|
||||
const char *vals[4] = { "repoattr", "rootattr", "missingattr", "subattr" };
|
||||
|
||||
/* it should be legal to look up values into the same array that has
|
||||
* the attribute names, overwriting each name as the value is found.
|
||||
*/
|
||||
|
||||
cl_git_pass(git_attr_get_many(vals, g_repo, 0, "sub/subdir_test1", 4, vals));
|
||||
|
||||
cl_assert(GIT_ATTR_TRUE(vals[0]));
|
||||
cl_assert(GIT_ATTR_TRUE(vals[1]));
|
||||
cl_assert(GIT_ATTR_UNSPECIFIED(vals[2]));
|
||||
cl_assert_equal_s("yes", vals[3]);
|
||||
}
|
||||
|
||||
static int count_attrs(
|
||||
const char *name,
|
||||
const char *value,
|
||||
|
||||
@ -3,22 +3,6 @@
|
||||
#include "refs.h"
|
||||
#include "fileops.h"
|
||||
|
||||
/* this is essentially the code from git__unescape modified slightly */
|
||||
void strip_cr_from_buf(git_buf *buf)
|
||||
{
|
||||
char *scan, *pos = buf->ptr, *end = pos + buf->size;
|
||||
|
||||
for (scan = pos; scan < end; pos++, scan++) {
|
||||
if (*scan == '\r')
|
||||
scan++; /* skip '\r' */
|
||||
if (pos != scan)
|
||||
*pos = *scan;
|
||||
}
|
||||
|
||||
*pos = '\0';
|
||||
buf->size = (pos - buf->ptr);
|
||||
}
|
||||
|
||||
void assert_on_branch(git_repository *repo, const char *branch)
|
||||
{
|
||||
git_reference *head;
|
||||
@ -50,48 +34,6 @@ void reset_index_to_treeish(git_object *treeish)
|
||||
git_index_free(index);
|
||||
}
|
||||
|
||||
static void check_file_contents_internal(
|
||||
const char *path,
|
||||
const char *expected_content,
|
||||
bool strip_cr,
|
||||
const char *file,
|
||||
int line,
|
||||
const char *msg)
|
||||
{
|
||||
int fd;
|
||||
char data[1024] = {0};
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
size_t expected_len = expected_content ? strlen(expected_content) : 0;
|
||||
|
||||
fd = p_open(path, O_RDONLY);
|
||||
cl_assert(fd >= 0);
|
||||
|
||||
buf.ptr = data;
|
||||
buf.size = p_read(fd, buf.ptr, sizeof(data));
|
||||
|
||||
cl_git_pass(p_close(fd));
|
||||
|
||||
if (strip_cr)
|
||||
strip_cr_from_buf(&buf);
|
||||
|
||||
clar__assert_equal(file, line, "strlen(expected_content) != strlen(actual_content)", 1, PRIuZ, expected_len, (size_t)buf.size);
|
||||
clar__assert_equal(file, line, msg, 1, "%s", expected_content, buf.ptr);
|
||||
}
|
||||
|
||||
void check_file_contents_at_line(
|
||||
const char *path, const char *expected,
|
||||
const char *file, int line, const char *msg)
|
||||
{
|
||||
check_file_contents_internal(path, expected, false, file, line, msg);
|
||||
}
|
||||
|
||||
void check_file_contents_nocr_at_line(
|
||||
const char *path, const char *expected,
|
||||
const char *file, int line, const char *msg)
|
||||
{
|
||||
check_file_contents_internal(path, expected, true, file, line, msg);
|
||||
}
|
||||
|
||||
int checkout_count_callback(
|
||||
git_checkout_notify_t why,
|
||||
const char *path,
|
||||
|
||||
@ -2,23 +2,14 @@
|
||||
#include "git2/object.h"
|
||||
#include "git2/repository.h"
|
||||
|
||||
extern void strip_cr_from_buf(git_buf *buf);
|
||||
extern void assert_on_branch(git_repository *repo, const char *branch);
|
||||
extern void reset_index_to_treeish(git_object *treeish);
|
||||
|
||||
extern void check_file_contents_at_line(
|
||||
const char *path, const char *expected,
|
||||
const char *file, int line, const char *msg);
|
||||
|
||||
extern void check_file_contents_nocr_at_line(
|
||||
const char *path, const char *expected,
|
||||
const char *file, int line, const char *msg);
|
||||
|
||||
#define check_file_contents(PATH,EXP) \
|
||||
check_file_contents_at_line(PATH,EXP,__FILE__,__LINE__,"String mismatch: " #EXP " != " #PATH)
|
||||
cl_assert_equal_file(EXP,0,PATH)
|
||||
|
||||
#define check_file_contents_nocr(PATH,EXP) \
|
||||
check_file_contents_nocr_at_line(PATH,EXP,__FILE__,__LINE__,"String mismatch: " #EXP " != " #PATH)
|
||||
cl_assert_equal_file_ignore_cr(EXP,0,PATH)
|
||||
|
||||
typedef struct {
|
||||
int n_conflicts;
|
||||
|
||||
@ -1,18 +1,10 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "checkout_helpers.h"
|
||||
#include "../filter/crlf.h"
|
||||
|
||||
#include "git2/checkout.h"
|
||||
#include "repository.h"
|
||||
|
||||
#define UTF8_BOM "\xEF\xBB\xBF"
|
||||
#define ALL_CRLF_TEXT_RAW "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n"
|
||||
#define ALL_LF_TEXT_RAW "lf\nlf\nlf\nlf\nlf\n"
|
||||
#define MORE_CRLF_TEXT_RAW "crlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf\r\n"
|
||||
#define MORE_LF_TEXT_RAW "lf\nlf\ncrlf\r\nlf\nlf\n"
|
||||
|
||||
#define ALL_LF_TEXT_AS_CRLF "lf\r\nlf\r\nlf\r\nlf\r\nlf\r\n"
|
||||
#define MORE_CRLF_TEXT_AS_CRLF "crlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf\r\n"
|
||||
#define MORE_LF_TEXT_AS_CRLF "lf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\n"
|
||||
#include "posix.h"
|
||||
|
||||
static git_repository *g_repo;
|
||||
|
||||
@ -145,3 +137,95 @@ void test_checkout_crlf__autocrlf_true_index_size_is_filtered_size(void)
|
||||
|
||||
git_index_free(index);
|
||||
}
|
||||
|
||||
void test_checkout_crlf__with_ident(void)
|
||||
{
|
||||
git_index *index;
|
||||
git_blob *blob;
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
|
||||
cl_git_mkfile("crlf/.gitattributes",
|
||||
"*.txt text\n*.bin binary\n"
|
||||
"*.crlf text eol=crlf\n"
|
||||
"*.lf text eol=lf\n"
|
||||
"*.ident text ident\n"
|
||||
"*.identcrlf ident text eol=crlf\n"
|
||||
"*.identlf ident text eol=lf\n");
|
||||
|
||||
cl_repo_set_bool(g_repo, "core.autocrlf", true);
|
||||
|
||||
/* add files with $Id$ */
|
||||
|
||||
cl_git_mkfile("crlf/lf.ident", ALL_LF_TEXT_RAW "\n$Id: initial content$\n");
|
||||
cl_git_mkfile("crlf/crlf.ident", ALL_CRLF_TEXT_RAW "\r\n$Id$\r\n\r\n");
|
||||
cl_git_mkfile("crlf/more1.identlf", "$Id$\n" MORE_LF_TEXT_RAW);
|
||||
cl_git_mkfile("crlf/more2.identcrlf", "\r\n$Id: $\r\n" MORE_CRLF_TEXT_RAW);
|
||||
|
||||
cl_git_pass(git_repository_index(&index, g_repo));
|
||||
cl_git_pass(git_index_add_bypath(index, "lf.ident"));
|
||||
cl_git_pass(git_index_add_bypath(index, "crlf.ident"));
|
||||
cl_git_pass(git_index_add_bypath(index, "more1.identlf"));
|
||||
cl_git_pass(git_index_add_bypath(index, "more2.identcrlf"));
|
||||
cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "Some ident files\n");
|
||||
|
||||
git_checkout_head(g_repo, &opts);
|
||||
|
||||
/* check that blobs have $Id$ */
|
||||
|
||||
cl_git_pass(git_blob_lookup(&blob, g_repo,
|
||||
& git_index_get_bypath(index, "lf.ident", 0)->oid));
|
||||
cl_assert_equal_s(
|
||||
ALL_LF_TEXT_RAW "\n$Id$\n", git_blob_rawcontent(blob));
|
||||
git_blob_free(blob);
|
||||
|
||||
cl_git_pass(git_blob_lookup(&blob, g_repo,
|
||||
& git_index_get_bypath(index, "more2.identcrlf", 0)->oid));
|
||||
cl_assert_equal_s(
|
||||
"\n$Id$\n" MORE_CRLF_TEXT_AS_LF, git_blob_rawcontent(blob));
|
||||
git_blob_free(blob);
|
||||
|
||||
/* check that filesystem is initially untouched - matching core Git */
|
||||
|
||||
cl_assert_equal_file(
|
||||
ALL_LF_TEXT_RAW "\n$Id: initial content$\n", 0, "crlf/lf.ident");
|
||||
|
||||
/* check that forced checkout rewrites correctly */
|
||||
|
||||
p_unlink("crlf/lf.ident");
|
||||
p_unlink("crlf/crlf.ident");
|
||||
p_unlink("crlf/more1.identlf");
|
||||
p_unlink("crlf/more2.identcrlf");
|
||||
|
||||
git_checkout_head(g_repo, &opts);
|
||||
|
||||
if (GIT_EOL_NATIVE == GIT_EOL_LF) {
|
||||
cl_assert_equal_file(
|
||||
ALL_LF_TEXT_RAW
|
||||
"\n$Id: fcf6d4d9c212dc66563b1171b1cd99953c756467$\n",
|
||||
0, "crlf/lf.ident");
|
||||
cl_assert_equal_file(
|
||||
ALL_CRLF_TEXT_AS_LF
|
||||
"\n$Id: f2c66ad9b2b5a734d9bf00d5000cc10a62b8a857$\n\n",
|
||||
0, "crlf/crlf.ident");
|
||||
} else {
|
||||
cl_assert_equal_file(
|
||||
ALL_LF_TEXT_AS_CRLF
|
||||
"\r\n$Id: fcf6d4d9c212dc66563b1171b1cd99953c756467$\r\n",
|
||||
0, "crlf/lf.ident");
|
||||
cl_assert_equal_file(
|
||||
ALL_CRLF_TEXT_RAW
|
||||
"\r\n$Id: f2c66ad9b2b5a734d9bf00d5000cc10a62b8a857$\r\n\r\n",
|
||||
0, "crlf/crlf.ident");
|
||||
}
|
||||
|
||||
cl_assert_equal_file(
|
||||
"$Id: f7830382dac1f1583422be5530fdfbd26289431b$\n"
|
||||
MORE_LF_TEXT_AS_LF, 0, "crlf/more1.identlf");
|
||||
|
||||
cl_assert_equal_file(
|
||||
"\r\n$Id: 74677a68413012ce8d7e7cfc3f12603df3a3eac4$\r\n"
|
||||
MORE_CRLF_TEXT_AS_CRLF, 0, "crlf/more2.identcrlf");
|
||||
|
||||
git_index_free(index);
|
||||
}
|
||||
|
||||
@ -16,11 +16,11 @@ void test_checkout_head__cleanup(void)
|
||||
cl_git_sandbox_cleanup();
|
||||
}
|
||||
|
||||
void test_checkout_head__orphaned_head_returns_GIT_EORPHANEDHEAD(void)
|
||||
void test_checkout_head__unborn_head_returns_GIT_EUNBORNBRANCH(void)
|
||||
{
|
||||
make_head_orphaned(g_repo, NON_EXISTING_HEAD);
|
||||
make_head_unborn(g_repo, NON_EXISTING_HEAD);
|
||||
|
||||
cl_assert_equal_i(GIT_EORPHANEDHEAD, git_checkout_head(g_repo, NULL));
|
||||
cl_assert_equal_i(GIT_EUNBORNBRANCH, git_checkout_head(g_repo, NULL));
|
||||
}
|
||||
|
||||
void test_checkout_head__with_index_only_tree(void)
|
||||
|
||||
@ -68,7 +68,6 @@ void cl_fixture_cleanup(const char *fixture_name);
|
||||
|
||||
#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2))
|
||||
|
||||
#define cl_assert_equal_sz(sz1,sz2) clar__assert_equal(__FILE__,__LINE__,#sz1 " != " #sz2, 1, "%"PRIuZ, (size_t)(sz1), (size_t)(sz2))
|
||||
|
||||
void clar__fail(
|
||||
const char *file,
|
||||
|
||||
@ -30,24 +30,26 @@ void cl_git_mkfile(const char *filename, const char *content)
|
||||
}
|
||||
|
||||
void cl_git_write2file(
|
||||
const char *filename, const char *new_content, int flags, unsigned int mode)
|
||||
const char *path, const char *content, size_t content_len,
|
||||
int flags, unsigned int mode)
|
||||
{
|
||||
int fd = p_open(filename, flags, mode);
|
||||
cl_assert(fd >= 0);
|
||||
if (!new_content)
|
||||
new_content = "\n";
|
||||
cl_must_pass(p_write(fd, new_content, strlen(new_content)));
|
||||
int fd;
|
||||
cl_assert(path && content);
|
||||
cl_assert((fd = p_open(path, flags, mode)) >= 0);
|
||||
if (!content_len)
|
||||
content_len = strlen(content);
|
||||
cl_must_pass(p_write(fd, content, content_len));
|
||||
cl_must_pass(p_close(fd));
|
||||
}
|
||||
|
||||
void cl_git_append2file(const char *filename, const char *new_content)
|
||||
void cl_git_append2file(const char *path, const char *content)
|
||||
{
|
||||
cl_git_write2file(filename, new_content, O_WRONLY | O_CREAT | O_APPEND, 0644);
|
||||
cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_APPEND, 0644);
|
||||
}
|
||||
|
||||
void cl_git_rewritefile(const char *filename, const char *new_content)
|
||||
void cl_git_rewritefile(const char *path, const char *content)
|
||||
{
|
||||
cl_git_write2file(filename, new_content, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
}
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
@ -337,6 +339,65 @@ int cl_git_remove_placeholders(const char *directory_path, const char *filename)
|
||||
return error;
|
||||
}
|
||||
|
||||
#define CL_COMMIT_NAME "Libgit2 Tester"
|
||||
#define CL_COMMIT_EMAIL "libgit2-test@github.com"
|
||||
#define CL_COMMIT_MSG "Test commit of tree "
|
||||
|
||||
void cl_repo_commit_from_index(
|
||||
git_oid *out,
|
||||
git_repository *repo,
|
||||
git_signature *sig,
|
||||
git_time_t time,
|
||||
const char *msg)
|
||||
{
|
||||
git_index *index;
|
||||
git_oid commit_id, tree_id;
|
||||
git_object *parent = NULL;
|
||||
git_reference *ref = NULL;
|
||||
git_tree *tree = NULL;
|
||||
char buf[128];
|
||||
int free_sig = (sig == NULL);
|
||||
|
||||
/* it is fine if looking up HEAD fails - we make this the first commit */
|
||||
git_revparse_ext(&parent, &ref, repo, "HEAD");
|
||||
|
||||
/* write the index content as a tree */
|
||||
cl_git_pass(git_repository_index(&index, repo));
|
||||
cl_git_pass(git_index_write_tree(&tree_id, index));
|
||||
cl_git_pass(git_index_write(index));
|
||||
git_index_free(index);
|
||||
|
||||
cl_git_pass(git_tree_lookup(&tree, repo, &tree_id));
|
||||
|
||||
if (sig)
|
||||
cl_assert(sig->name && sig->email);
|
||||
else if (!time)
|
||||
cl_git_pass(git_signature_now(&sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL));
|
||||
else
|
||||
cl_git_pass(git_signature_new(
|
||||
&sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL, time, 0));
|
||||
|
||||
if (!msg) {
|
||||
strcpy(buf, CL_COMMIT_MSG);
|
||||
git_oid_tostr(buf + strlen(CL_COMMIT_MSG),
|
||||
sizeof(buf) - strlen(CL_COMMIT_MSG), &tree_id);
|
||||
msg = buf;
|
||||
}
|
||||
|
||||
cl_git_pass(git_commit_create_v(
|
||||
&commit_id, repo, ref ? git_reference_name(ref) : "HEAD",
|
||||
sig, sig, NULL, msg, tree, parent ? 1 : 0, parent));
|
||||
|
||||
if (out)
|
||||
git_oid_cpy(out, &commit_id);
|
||||
|
||||
git_object_free(parent);
|
||||
git_reference_free(ref);
|
||||
if (free_sig)
|
||||
git_signature_free(sig);
|
||||
git_tree_free(tree);
|
||||
}
|
||||
|
||||
void cl_repo_set_bool(git_repository *repo, const char *cfg, int value)
|
||||
{
|
||||
git_config *config;
|
||||
@ -354,3 +415,65 @@ int cl_repo_get_bool(git_repository *repo, const char *cfg)
|
||||
git_config_free(config);
|
||||
return val;
|
||||
}
|
||||
|
||||
/* this is essentially the code from git__unescape modified slightly */
|
||||
static size_t strip_cr_from_buf(char *start, size_t len)
|
||||
{
|
||||
char *scan, *trail, *end = start + len;
|
||||
|
||||
for (scan = trail = start; scan < end; trail++, scan++) {
|
||||
while (*scan == '\r')
|
||||
scan++; /* skip '\r' */
|
||||
|
||||
if (trail != scan)
|
||||
*trail = *scan;
|
||||
}
|
||||
|
||||
*trail = '\0';
|
||||
|
||||
return (trail - start);
|
||||
}
|
||||
|
||||
void clar__assert_equal_file(
|
||||
const char *expected_data,
|
||||
size_t expected_bytes,
|
||||
int ignore_cr,
|
||||
const char *path,
|
||||
const char *file,
|
||||
size_t line)
|
||||
{
|
||||
char buf[4000];
|
||||
ssize_t bytes, total_bytes = 0;
|
||||
int fd = p_open(path, O_RDONLY | O_BINARY);
|
||||
cl_assert(fd >= 0);
|
||||
|
||||
if (expected_data && !expected_bytes)
|
||||
expected_bytes = strlen(expected_data);
|
||||
|
||||
while ((bytes = p_read(fd, buf, sizeof(buf))) != 0) {
|
||||
clar__assert(
|
||||
bytes > 0, file, line, "error reading from file", path, 1);
|
||||
|
||||
if (ignore_cr)
|
||||
bytes = strip_cr_from_buf(buf, bytes);
|
||||
|
||||
if (memcmp(expected_data, buf, bytes) != 0) {
|
||||
int pos;
|
||||
for (pos = 0; pos < bytes && expected_data[pos] == buf[pos]; ++pos)
|
||||
/* find differing byte offset */;
|
||||
p_snprintf(
|
||||
buf, sizeof(buf), "file content mismatch at byte %d",
|
||||
(int)(total_bytes + pos));
|
||||
clar__fail(file, line, buf, path, 1);
|
||||
}
|
||||
|
||||
expected_data += bytes;
|
||||
total_bytes += bytes;
|
||||
}
|
||||
|
||||
p_close(fd);
|
||||
|
||||
clar__assert(!bytes, file, line, "error reading from file", path, 1);
|
||||
clar__assert_equal(file, line, "mismatched file length", 1, "%"PRIuZ,
|
||||
(size_t)expected_bytes, (size_t)total_bytes);
|
||||
}
|
||||
|
||||
@ -43,9 +43,28 @@ GIT_INLINE(void) clar__assert_in_range(
|
||||
}
|
||||
}
|
||||
|
||||
#define cl_assert_equal_sz(sz1,sz2) do { \
|
||||
size_t __sz1 = (sz1), __sz2 = (sz2); \
|
||||
clar__assert_equal(__FILE__,__LINE__,#sz1 " != " #sz2, 1, "%"PRIuZ, __sz1, __sz2); \
|
||||
} while (0)
|
||||
|
||||
#define cl_assert_in_range(L,V,H) \
|
||||
clar__assert_in_range((L),(V),(H),__FILE__,__LINE__,"Range check: " #V " in [" #L "," #H "]", 1)
|
||||
|
||||
#define cl_assert_equal_file(DATA,SIZE,PATH) \
|
||||
clar__assert_equal_file(DATA,SIZE,0,PATH,__FILE__,__LINE__)
|
||||
|
||||
#define cl_assert_equal_file_ignore_cr(DATA,SIZE,PATH) \
|
||||
clar__assert_equal_file(DATA,SIZE,1,PATH,__FILE__,__LINE__)
|
||||
|
||||
void clar__assert_equal_file(
|
||||
const char *expected_data,
|
||||
size_t expected_size,
|
||||
int ignore_cr,
|
||||
const char *path,
|
||||
const char *file,
|
||||
size_t line);
|
||||
|
||||
/*
|
||||
* Some utility macros for building long strings
|
||||
*/
|
||||
@ -59,7 +78,8 @@ GIT_INLINE(void) clar__assert_in_range(
|
||||
void cl_git_mkfile(const char *filename, const char *content);
|
||||
void cl_git_append2file(const char *filename, const char *new_content);
|
||||
void cl_git_rewritefile(const char *filename, const char *new_content);
|
||||
void cl_git_write2file(const char *filename, const char *new_content, int flags, unsigned int mode);
|
||||
void cl_git_write2file(const char *path, const char *data,
|
||||
size_t datalen, int flags, unsigned int mode);
|
||||
|
||||
bool cl_toggle_filemode(const char *filename);
|
||||
bool cl_is_chmod_supported(void);
|
||||
@ -84,6 +104,14 @@ const char* cl_git_path_url(const char *path);
|
||||
/* Test repository cleaner */
|
||||
int cl_git_remove_placeholders(const char *directory_path, const char *filename);
|
||||
|
||||
/* commit creation helpers */
|
||||
void cl_repo_commit_from_index(
|
||||
git_oid *out,
|
||||
git_repository *repo,
|
||||
git_signature *sig,
|
||||
git_time_t time,
|
||||
const char *msg);
|
||||
|
||||
/* config setting helpers */
|
||||
void cl_repo_set_bool(git_repository *repo, const char *cfg, int value);
|
||||
int cl_repo_get_bool(git_repository *repo, const char *cfg);
|
||||
|
||||
@ -44,7 +44,7 @@ void test_clone_empty__can_clone_an_empty_local_repo_barely(void)
|
||||
g_options.bare = true;
|
||||
cl_git_pass(git_clone(&g_repo_cloned, "./empty_bare.git", "./empty", &g_options));
|
||||
|
||||
/* Although the HEAD is orphaned... */
|
||||
/* Although the HEAD is unborn... */
|
||||
cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo_cloned, local_name));
|
||||
|
||||
/* ...one can still retrieve the name of the remote tracking reference */
|
||||
@ -59,7 +59,7 @@ void test_clone_empty__can_clone_an_empty_local_repo_barely(void)
|
||||
|
||||
cl_assert_equal_s(expected_remote_name, buffer);
|
||||
|
||||
/* ...even when the remote HEAD is orphaned as well */
|
||||
/* ...even when the remote HEAD is unborn as well */
|
||||
cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo_cloned,
|
||||
expected_tracked_branch_name));
|
||||
}
|
||||
|
||||
@ -228,7 +228,7 @@ void test_clone_nonetwork__can_checkout_given_branch(void)
|
||||
g_options.checkout_branch = "test";
|
||||
cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
|
||||
|
||||
cl_assert_equal_i(0, git_repository_head_orphan(g_repo));
|
||||
cl_assert_equal_i(0, git_repository_head_unborn(g_repo));
|
||||
|
||||
cl_git_pass(git_repository_head(&g_ref, g_repo));
|
||||
cl_assert_equal_s(git_reference_name(g_ref), "refs/heads/test");
|
||||
|
||||
@ -6,19 +6,22 @@ void test_config_global__initialize(void)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
|
||||
cl_must_pass(p_mkdir("home", 0777));
|
||||
cl_assert_equal_i(0, p_mkdir("home", 0777));
|
||||
cl_git_pass(git_path_prettify(&path, "home", NULL));
|
||||
cl_git_pass(git_libgit2_opts(
|
||||
GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
|
||||
|
||||
cl_must_pass(p_mkdir("xdg", 0777));
|
||||
cl_git_pass(git_path_prettify(&path, "xdg", NULL));
|
||||
cl_assert_equal_i(0, p_mkdir("xdg", 0777));
|
||||
cl_assert_equal_i(0, p_mkdir("xdg/git", 0777));
|
||||
cl_git_pass(git_path_prettify(&path, "xdg/git", NULL));
|
||||
cl_git_pass(git_libgit2_opts(
|
||||
GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr));
|
||||
|
||||
cl_assert_equal_i(0, p_mkdir("etc", 0777));
|
||||
cl_git_pass(git_path_prettify(&path, "etc", NULL));
|
||||
cl_git_pass(git_libgit2_opts(
|
||||
GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr));
|
||||
|
||||
cl_git_pass(git_libgit2_opts(
|
||||
GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, NULL));
|
||||
|
||||
git_buf_free(&path);
|
||||
}
|
||||
|
||||
@ -26,6 +29,11 @@ void test_config_global__cleanup(void)
|
||||
{
|
||||
cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES));
|
||||
cl_git_pass(git_futils_rmdir_r("xdg", NULL, GIT_RMDIR_REMOVE_FILES));
|
||||
cl_git_pass(git_futils_rmdir_r("etc", NULL, GIT_RMDIR_REMOVE_FILES));
|
||||
|
||||
git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, NULL);
|
||||
git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, NULL);
|
||||
git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, NULL);
|
||||
}
|
||||
|
||||
void test_config_global__open_global(void)
|
||||
@ -48,10 +56,7 @@ void test_config_global__open_xdg(void)
|
||||
const char *val, *str = "teststring";
|
||||
const char *key = "this.variable";
|
||||
|
||||
p_setenv("XDG_CONFIG_HOME", "xdg", 1);
|
||||
|
||||
cl_must_pass(p_mkdir("xdg/git/", 0777));
|
||||
cl_git_mkfile("xdg/git/config", "");
|
||||
cl_git_mkfile("xdg/git/config", "# XDG config\n[core]\n test = 1\n");
|
||||
|
||||
cl_git_pass(git_config_open_default(&cfg));
|
||||
cl_git_pass(git_config_open_level(&xdg, cfg, GIT_CONFIG_LEVEL_XDG));
|
||||
|
||||
109
tests-clar/config/include.c
Normal file
109
tests-clar/config/include.c
Normal file
@ -0,0 +1,109 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "buffer.h"
|
||||
#include "fileops.h"
|
||||
|
||||
void test_config_include__relative(void)
|
||||
{
|
||||
git_config *cfg;
|
||||
const char *str;
|
||||
|
||||
cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config-include")));
|
||||
|
||||
cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
|
||||
cl_assert_equal_s(str, "huzzah");
|
||||
|
||||
git_config_free(cfg);
|
||||
}
|
||||
|
||||
void test_config_include__absolute(void)
|
||||
{
|
||||
git_config *cfg;
|
||||
const char *str;
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_buf_printf(&buf, "[include]\npath = %s/config-included", cl_fixture("config")));
|
||||
|
||||
cl_git_mkfile("config-include-absolute", git_buf_cstr(&buf));
|
||||
git_buf_free(&buf);
|
||||
cl_git_pass(git_config_open_ondisk(&cfg, "config-include-absolute"));
|
||||
|
||||
cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
|
||||
cl_assert_equal_s(str, "huzzah");
|
||||
|
||||
git_config_free(cfg);
|
||||
}
|
||||
|
||||
void test_config_include__homedir(void)
|
||||
{
|
||||
git_config *cfg;
|
||||
const char *str;
|
||||
|
||||
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, cl_fixture("config")));
|
||||
cl_git_mkfile("config-include-homedir", "[include]\npath = ~/config-included");
|
||||
|
||||
cl_git_pass(git_config_open_ondisk(&cfg, "config-include-homedir"));
|
||||
|
||||
cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
|
||||
cl_assert_equal_s(str, "huzzah");
|
||||
|
||||
git_config_free(cfg);
|
||||
}
|
||||
|
||||
void test_config_include__refresh(void)
|
||||
{
|
||||
git_config *cfg;
|
||||
const char *str;
|
||||
|
||||
cl_fixture_sandbox("config");
|
||||
|
||||
cl_git_pass(git_config_open_ondisk(&cfg, "config/config-include"));
|
||||
|
||||
cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
|
||||
cl_assert_equal_s(str, "huzzah");
|
||||
|
||||
/* Change the included file and see if we refresh */
|
||||
cl_git_mkfile("config/config-included", "[foo \"bar\"]\nbaz = hurrah");
|
||||
cl_git_pass(git_config_refresh(cfg));
|
||||
|
||||
cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
|
||||
cl_assert_equal_s(str, "hurrah");
|
||||
|
||||
git_config_free(cfg);
|
||||
cl_fixture_cleanup("config");
|
||||
}
|
||||
|
||||
/* We need to pretend that the variables were defined where the file was included */
|
||||
void test_config_include__ordering(void)
|
||||
{
|
||||
git_config *cfg;
|
||||
const char *str;
|
||||
|
||||
cl_git_mkfile("included", "[foo \"bar\"]\nbaz = hurrah\nfrotz = hiya");
|
||||
cl_git_mkfile("including",
|
||||
"[foo \"bar\"]\nfrotz = hello\n"
|
||||
"[include]\npath = included\n"
|
||||
"[foo \"bar\"]\nbaz = huzzah\n");
|
||||
|
||||
cl_git_pass(git_config_open_ondisk(&cfg, "including"));
|
||||
|
||||
cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.frotz"));
|
||||
cl_assert_equal_s(str, "hiya");
|
||||
cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
|
||||
cl_assert_equal_s(str, "huzzah");
|
||||
|
||||
git_config_free(cfg);
|
||||
}
|
||||
|
||||
/* We need to pretend that the variables were defined where the file was included */
|
||||
void test_config_include__depth(void)
|
||||
{
|
||||
git_config *cfg;
|
||||
|
||||
cl_git_mkfile("a", "[include]\npath = b");
|
||||
cl_git_mkfile("b", "[include]\npath = a");
|
||||
|
||||
cl_git_fail(git_config_open_ondisk(&cfg, "a"));
|
||||
|
||||
unlink("a");
|
||||
unlink("b");
|
||||
}
|
||||
@ -523,3 +523,18 @@ void test_config_read__corrupt_header(void)
|
||||
|
||||
git_config_free(cfg);
|
||||
}
|
||||
|
||||
void test_config_read__override_variable(void)
|
||||
{
|
||||
git_config *cfg;
|
||||
const char *str;
|
||||
|
||||
cl_set_cleanup(&clean_test_config, NULL);
|
||||
cl_git_mkfile("./testconfig", "[some] var = one\nvar = two");
|
||||
cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig"));
|
||||
|
||||
cl_git_pass(git_config_get_string(&str, cfg, "some.var"));
|
||||
cl_assert_equal_s(str, "two");
|
||||
|
||||
git_config_free(cfg);
|
||||
}
|
||||
|
||||
@ -919,6 +919,8 @@ void test_core_buffer__similarity_metric_whitespace(void)
|
||||
git_buf_free(&buf);
|
||||
}
|
||||
|
||||
#include "../filter/crlf.h"
|
||||
|
||||
#define check_buf(expected,buf) do { \
|
||||
cl_assert_equal_s(expected, buf.ptr); \
|
||||
cl_assert_equal_sz(strlen(expected), buf.size); } while (0)
|
||||
@ -934,16 +936,16 @@ void test_core_buffer__lf_and_crlf_conversions(void)
|
||||
cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
|
||||
check_buf("lf\r\nlf\r\nlf\r\nlf\r\n", tgt);
|
||||
|
||||
cl_assert_equal_i(GIT_ENOTFOUND, git_buf_text_crlf_to_lf(&tgt, &src));
|
||||
/* no conversion needed if all LFs already */
|
||||
cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
|
||||
check_buf(src.ptr, tgt);
|
||||
|
||||
git_buf_sets(&src, "\nlf\nlf\nlf\nlf\nlf");
|
||||
|
||||
cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
|
||||
check_buf("\r\nlf\r\nlf\r\nlf\r\nlf\r\nlf", tgt);
|
||||
|
||||
cl_assert_equal_i(GIT_ENOTFOUND, git_buf_text_crlf_to_lf(&tgt, &src));
|
||||
/* no conversion needed if all LFs already */
|
||||
cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
|
||||
check_buf(src.ptr, tgt);
|
||||
|
||||
/* CRLF source */
|
||||
|
||||
@ -993,10 +995,45 @@ void test_core_buffer__lf_and_crlf_conversions(void)
|
||||
check_buf("\rcrlf\nlf\nlf\ncr\rcrlf\nlf\ncr\r", tgt);
|
||||
|
||||
git_buf_sets(&src, "\rcr\r");
|
||||
cl_assert_equal_i(GIT_ENOTFOUND, git_buf_text_lf_to_crlf(&tgt, &src));
|
||||
cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
|
||||
check_buf(src.ptr, tgt);
|
||||
cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
|
||||
check_buf("\rcr\r", tgt);
|
||||
|
||||
git_buf_free(&src);
|
||||
git_buf_free(&tgt);
|
||||
|
||||
/* blob correspondence tests */
|
||||
|
||||
git_buf_sets(&src, ALL_CRLF_TEXT_RAW);
|
||||
cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
|
||||
check_buf(ALL_CRLF_TEXT_AS_CRLF, tgt);
|
||||
cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
|
||||
check_buf(ALL_CRLF_TEXT_AS_LF, tgt);
|
||||
git_buf_free(&src);
|
||||
git_buf_free(&tgt);
|
||||
|
||||
git_buf_sets(&src, ALL_LF_TEXT_RAW);
|
||||
cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
|
||||
check_buf(ALL_LF_TEXT_AS_CRLF, tgt);
|
||||
cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
|
||||
check_buf(ALL_LF_TEXT_AS_LF, tgt);
|
||||
git_buf_free(&src);
|
||||
git_buf_free(&tgt);
|
||||
|
||||
git_buf_sets(&src, MORE_CRLF_TEXT_RAW);
|
||||
cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
|
||||
check_buf(MORE_CRLF_TEXT_AS_CRLF, tgt);
|
||||
cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
|
||||
check_buf(MORE_CRLF_TEXT_AS_LF, tgt);
|
||||
git_buf_free(&src);
|
||||
git_buf_free(&tgt);
|
||||
|
||||
git_buf_sets(&src, MORE_LF_TEXT_RAW);
|
||||
cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
|
||||
check_buf(MORE_LF_TEXT_AS_CRLF, tgt);
|
||||
cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
|
||||
check_buf(MORE_LF_TEXT_AS_LF, tgt);
|
||||
git_buf_free(&src);
|
||||
git_buf_free(&tgt);
|
||||
}
|
||||
|
||||
@ -24,18 +24,16 @@ void test_core_filebuf__0(void)
|
||||
void test_core_filebuf__1(void)
|
||||
{
|
||||
git_filebuf file = GIT_FILEBUF_INIT;
|
||||
int fd;
|
||||
char test[] = "test";
|
||||
|
||||
fd = p_creat(test, 0666); //-V536
|
||||
cl_must_pass(fd);
|
||||
cl_must_pass(p_write(fd, "libgit2 rocks\n", 14));
|
||||
cl_must_pass(p_close(fd));
|
||||
cl_git_mkfile(test, "libgit2 rocks\n");
|
||||
|
||||
cl_git_pass(git_filebuf_open(&file, test, GIT_FILEBUF_APPEND));
|
||||
cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
|
||||
cl_git_pass(git_filebuf_commit(&file, 0666));
|
||||
|
||||
cl_assert_equal_file("libgit2 rocks\nlibgit2 rocks\n", 0, test);
|
||||
|
||||
cl_must_pass(p_unlink(test));
|
||||
}
|
||||
|
||||
@ -53,6 +51,8 @@ void test_core_filebuf__2(void)
|
||||
cl_git_pass(git_filebuf_write(&file, buf, sizeof(buf)));
|
||||
cl_git_pass(git_filebuf_commit(&file, 0666));
|
||||
|
||||
cl_assert_equal_file((char *)buf, sizeof(buf), test);
|
||||
|
||||
cl_must_pass(p_unlink(test));
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,8 @@ static git_repository *g_repo = NULL;
|
||||
void test_diff_rename__initialize(void)
|
||||
{
|
||||
g_repo = cl_git_sandbox_init("renames");
|
||||
|
||||
cl_repo_set_bool(g_repo, "core.autocrlf", false);
|
||||
}
|
||||
|
||||
void test_diff_rename__cleanup(void)
|
||||
|
||||
@ -11,7 +11,6 @@ void test_diff_submodules__initialize(void)
|
||||
|
||||
void test_diff_submodules__cleanup(void)
|
||||
{
|
||||
cleanup_fixture_submodules();
|
||||
}
|
||||
|
||||
static void check_diff_patches_at_line(
|
||||
@ -229,11 +228,11 @@ void test_diff_submodules__invalid_cache(void)
|
||||
"<END>"
|
||||
};
|
||||
static const char *expected_moved[] = {
|
||||
"diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..0910a13 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 0910a13dfa2210496f6c590d75bc360dd11b2a1b\n",
|
||||
"diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..7002348 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 700234833f6ccc20d744b238612646be071acaae\n",
|
||||
"<END>"
|
||||
};
|
||||
static const char *expected_moved_dirty[] = {
|
||||
"diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..0910a13 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 0910a13dfa2210496f6c590d75bc360dd11b2a1b-dirty\n",
|
||||
"diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..7002348 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 700234833f6ccc20d744b238612646be071acaae-dirty\n",
|
||||
"<END>"
|
||||
};
|
||||
|
||||
@ -310,26 +309,7 @@ void test_diff_submodules__invalid_cache(void)
|
||||
git_diff_list_free(diff);
|
||||
|
||||
/* commit changed index of submodule */
|
||||
{
|
||||
git_object *parent;
|
||||
git_oid tree_id, commit_id;
|
||||
git_tree *tree;
|
||||
git_signature *sig;
|
||||
git_reference *ref;
|
||||
|
||||
cl_git_pass(git_revparse_ext(&parent, &ref, smrepo, "HEAD"));
|
||||
cl_git_pass(git_index_write_tree(&tree_id, smindex));
|
||||
cl_git_pass(git_index_write(smindex));
|
||||
cl_git_pass(git_tree_lookup(&tree, smrepo, &tree_id));
|
||||
cl_git_pass(git_signature_new(&sig, "Sm Test", "sm@tester.test", 1372350000, 480));
|
||||
cl_git_pass(git_commit_create_v(
|
||||
&commit_id, smrepo, git_reference_name(ref), sig, sig,
|
||||
NULL, "Move it", tree, 1, parent));
|
||||
git_object_free(parent);
|
||||
git_tree_free(tree);
|
||||
git_reference_free(ref);
|
||||
git_signature_free(sig);
|
||||
}
|
||||
cl_repo_commit_from_index(NULL, smrepo, NULL, 1372350000, "Move it");
|
||||
|
||||
git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_DIRTY);
|
||||
|
||||
|
||||
@ -1266,3 +1266,28 @@ void test_diff_workdir__untracked_directory_comes_last(void)
|
||||
|
||||
git_diff_list_free(diff);
|
||||
}
|
||||
|
||||
void test_diff_workdir__untracked_with_bom(void)
|
||||
{
|
||||
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
|
||||
git_diff_list *diff = NULL;
|
||||
const git_diff_delta *delta;
|
||||
|
||||
g_repo = cl_git_sandbox_init("empty_standard_repo");
|
||||
cl_repo_set_bool(g_repo, "core.autocrlf", true);
|
||||
|
||||
cl_git_write2file("empty_standard_repo/bom.txt",
|
||||
"\xFF\xFE\x31\x00\x32\x00\x33\x00\x34\x00", 10, O_WRONLY|O_CREAT, 0664);
|
||||
|
||||
opts.flags =
|
||||
GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_INCLUDE_UNTRACKED_CONTENT;
|
||||
|
||||
cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
|
||||
|
||||
cl_assert_equal_i(1, git_diff_num_deltas(diff));
|
||||
cl_git_pass(git_diff_get_patch(NULL, &delta, diff, 0));
|
||||
cl_assert_equal_i(GIT_DELTA_UNTRACKED, delta->status);
|
||||
cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0);
|
||||
|
||||
git_diff_list_free(diff);
|
||||
}
|
||||
|
||||
84
tests-clar/filter/blob.c
Normal file
84
tests-clar/filter/blob.c
Normal file
@ -0,0 +1,84 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "crlf.h"
|
||||
|
||||
static git_repository *g_repo = NULL;
|
||||
|
||||
void test_filter_blob__initialize(void)
|
||||
{
|
||||
g_repo = cl_git_sandbox_init("crlf");
|
||||
cl_git_mkfile("crlf/.gitattributes",
|
||||
"*.txt text\n*.bin binary\n"
|
||||
"*.crlf text eol=crlf\n"
|
||||
"*.lf text eol=lf\n"
|
||||
"*.ident text ident\n"
|
||||
"*.identcrlf ident text eol=crlf\n"
|
||||
"*.identlf ident text eol=lf\n");
|
||||
}
|
||||
|
||||
void test_filter_blob__cleanup(void)
|
||||
{
|
||||
cl_git_sandbox_cleanup();
|
||||
}
|
||||
|
||||
void test_filter_blob__all_crlf(void)
|
||||
{
|
||||
git_blob *blob;
|
||||
git_buf buf = { 0 };
|
||||
|
||||
cl_git_pass(git_revparse_single(
|
||||
(git_object **)&blob, g_repo, "a9a2e891")); /* all-crlf */
|
||||
|
||||
cl_assert_equal_s(ALL_CRLF_TEXT_RAW, git_blob_rawcontent(blob));
|
||||
|
||||
cl_git_pass(git_blob_filtered_content(&buf, blob, "file.bin", 1));
|
||||
|
||||
cl_assert_equal_s(ALL_CRLF_TEXT_RAW, buf.ptr);
|
||||
|
||||
cl_git_pass(git_blob_filtered_content(&buf, blob, "file.crlf", 1));
|
||||
|
||||
/* in this case, raw content has crlf in it already */
|
||||
cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr);
|
||||
|
||||
cl_git_pass(git_blob_filtered_content(&buf, blob, "file.lf", 1));
|
||||
|
||||
cl_assert_equal_s(ALL_CRLF_TEXT_AS_LF, buf.ptr);
|
||||
|
||||
git_buf_free(&buf);
|
||||
git_blob_free(blob);
|
||||
}
|
||||
|
||||
void test_filter_blob__ident(void)
|
||||
{
|
||||
git_oid id;
|
||||
git_blob *blob;
|
||||
git_buf buf = { 0 };
|
||||
|
||||
cl_git_mkfile("crlf/test.ident", "Some text\n$Id$\nGoes there\n");
|
||||
cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "test.ident"));
|
||||
cl_git_pass(git_blob_lookup(&blob, g_repo, &id));
|
||||
cl_assert_equal_s(
|
||||
"Some text\n$Id$\nGoes there\n", git_blob_rawcontent(blob));
|
||||
git_blob_free(blob);
|
||||
|
||||
cl_git_mkfile("crlf/test.ident", "Some text\n$Id: Any old just you want$\nGoes there\n");
|
||||
cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "test.ident"));
|
||||
cl_git_pass(git_blob_lookup(&blob, g_repo, &id));
|
||||
cl_assert_equal_s(
|
||||
"Some text\n$Id$\nGoes there\n", git_blob_rawcontent(blob));
|
||||
|
||||
cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.bin", 1));
|
||||
cl_assert_equal_s(
|
||||
"Some text\n$Id$\nGoes there\n", buf.ptr);
|
||||
|
||||
cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.identcrlf", 1));
|
||||
cl_assert_equal_s(
|
||||
"Some text\r\n$Id: 3164f585d548ac68027d22b104f2d8100b2b6845$\r\nGoes there\r\n", buf.ptr);
|
||||
|
||||
cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.identlf", 1));
|
||||
cl_assert_equal_s(
|
||||
"Some text\n$Id: 3164f585d548ac68027d22b104f2d8100b2b6845$\nGoes there\n", buf.ptr);
|
||||
|
||||
git_buf_free(&buf);
|
||||
git_blob_free(blob);
|
||||
|
||||
}
|
||||
71
tests-clar/filter/crlf.c
Normal file
71
tests-clar/filter/crlf.c
Normal file
@ -0,0 +1,71 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "git2/sys/filter.h"
|
||||
|
||||
static git_repository *g_repo = NULL;
|
||||
|
||||
void test_filter_crlf__initialize(void)
|
||||
{
|
||||
g_repo = cl_git_sandbox_init("crlf");
|
||||
|
||||
cl_git_mkfile("crlf/.gitattributes",
|
||||
"*.txt text\n*.bin binary\n*.crlf text eol=crlf\n*.lf text eol=lf\n");
|
||||
|
||||
cl_repo_set_bool(g_repo, "core.autocrlf", true);
|
||||
}
|
||||
|
||||
void test_filter_crlf__cleanup(void)
|
||||
{
|
||||
cl_git_sandbox_cleanup();
|
||||
}
|
||||
|
||||
void test_filter_crlf__to_worktree(void)
|
||||
{
|
||||
git_filter_list *fl;
|
||||
git_filter *crlf;
|
||||
git_buf in = { 0 }, out = { 0 };
|
||||
|
||||
cl_git_pass(git_filter_list_new(&fl, g_repo, GIT_FILTER_TO_WORKTREE));
|
||||
|
||||
crlf = git_filter_lookup(GIT_FILTER_CRLF);
|
||||
cl_assert(crlf != NULL);
|
||||
|
||||
cl_git_pass(git_filter_list_push(fl, crlf, NULL));
|
||||
|
||||
in.ptr = "Some text\nRight here\n";
|
||||
in.size = strlen(in.ptr);
|
||||
|
||||
cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
cl_assert_equal_s("Some text\r\nRight here\r\n", out.ptr);
|
||||
#else
|
||||
cl_assert_equal_s("Some text\nRight here\n", out.ptr);
|
||||
#endif
|
||||
|
||||
git_filter_list_free(fl);
|
||||
git_buf_free(&out);
|
||||
}
|
||||
|
||||
void test_filter_crlf__to_odb(void)
|
||||
{
|
||||
git_filter_list *fl;
|
||||
git_filter *crlf;
|
||||
git_buf in = { 0 }, out = { 0 };
|
||||
|
||||
cl_git_pass(git_filter_list_new(&fl, g_repo, GIT_FILTER_TO_ODB));
|
||||
|
||||
crlf = git_filter_lookup(GIT_FILTER_CRLF);
|
||||
cl_assert(crlf != NULL);
|
||||
|
||||
cl_git_pass(git_filter_list_push(fl, crlf, NULL));
|
||||
|
||||
in.ptr = "Some text\r\nRight here\r\n";
|
||||
in.size = strlen(in.ptr);
|
||||
|
||||
cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
|
||||
|
||||
cl_assert_equal_s("Some text\nRight here\n", out.ptr);
|
||||
|
||||
git_filter_list_free(fl);
|
||||
git_buf_free(&out);
|
||||
}
|
||||
25
tests-clar/filter/crlf.h
Normal file
25
tests-clar/filter/crlf.h
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef INCLUDE_filter_crlf_h__
|
||||
#define INCLUDE_filter_crlf_h__
|
||||
|
||||
/*
|
||||
* file content for files in the resources/crlf repository
|
||||
*/
|
||||
|
||||
#define UTF8_BOM "\xEF\xBB\xBF"
|
||||
|
||||
#define ALL_CRLF_TEXT_RAW "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n"
|
||||
#define ALL_LF_TEXT_RAW "lf\nlf\nlf\nlf\nlf\n"
|
||||
#define MORE_CRLF_TEXT_RAW "crlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf\r\n"
|
||||
#define MORE_LF_TEXT_RAW "lf\nlf\ncrlf\r\nlf\nlf\n"
|
||||
|
||||
#define ALL_CRLF_TEXT_AS_CRLF ALL_CRLF_TEXT_RAW
|
||||
#define ALL_LF_TEXT_AS_CRLF "lf\r\nlf\r\nlf\r\nlf\r\nlf\r\n"
|
||||
#define MORE_CRLF_TEXT_AS_CRLF "crlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf\r\n"
|
||||
#define MORE_LF_TEXT_AS_CRLF "lf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\n"
|
||||
|
||||
#define ALL_CRLF_TEXT_AS_LF "crlf\ncrlf\ncrlf\ncrlf\n"
|
||||
#define ALL_LF_TEXT_AS_LF ALL_LF_TEXT_RAW
|
||||
#define MORE_CRLF_TEXT_AS_LF "crlf\ncrlf\nlf\ncrlf\ncrlf\n"
|
||||
#define MORE_LF_TEXT_AS_LF "lf\nlf\ncrlf\nlf\nlf\n"
|
||||
|
||||
#endif
|
||||
337
tests-clar/filter/custom.c
Normal file
337
tests-clar/filter/custom.c
Normal file
@ -0,0 +1,337 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "posix.h"
|
||||
#include "blob.h"
|
||||
#include "filter.h"
|
||||
#include "buf_text.h"
|
||||
#include "git2/sys/filter.h"
|
||||
#include "git2/sys/repository.h"
|
||||
|
||||
/* going TO_WORKDIR, filters are executed low to high
|
||||
* going TO_ODB, filters are executed high to low
|
||||
*/
|
||||
#define BITFLIP_FILTER_PRIORITY -1
|
||||
#define REVERSE_FILTER_PRIORITY -2
|
||||
|
||||
#define VERY_SECURE_ENCRYPTION(b) ((b) ^ 0xff)
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
# define NEWLINE "\r\n"
|
||||
#else
|
||||
# define NEWLINE "\n"
|
||||
#endif
|
||||
|
||||
static char workdir_data[] =
|
||||
"some simple" NEWLINE
|
||||
"data" NEWLINE
|
||||
"that will be" NEWLINE
|
||||
"trivially" NEWLINE
|
||||
"scrambled." NEWLINE;
|
||||
|
||||
/* Represents the data above scrambled (bits flipped) after \r\n -> \n
|
||||
* conversion, then bytewise reversed
|
||||
*/
|
||||
static unsigned char bitflipped_and_reversed_data[] =
|
||||
{ 0xf5, 0xd1, 0x9b, 0x9a, 0x93, 0x9d, 0x92, 0x9e, 0x8d, 0x9c, 0x8c,
|
||||
0xf5, 0x86, 0x93, 0x93, 0x9e, 0x96, 0x89, 0x96, 0x8d, 0x8b, 0xf5,
|
||||
0x9a, 0x9d, 0xdf, 0x93, 0x93, 0x96, 0x88, 0xdf, 0x8b, 0x9e, 0x97,
|
||||
0x8b, 0xf5, 0x9e, 0x8b, 0x9e, 0x9b, 0xf5, 0x9a, 0x93, 0x8f, 0x92,
|
||||
0x96, 0x8c, 0xdf, 0x9a, 0x92, 0x90, 0x8c };
|
||||
|
||||
#define BITFLIPPED_AND_REVERSED_DATA_LEN 51
|
||||
|
||||
static git_repository *g_repo = NULL;
|
||||
|
||||
static void register_custom_filters(void);
|
||||
|
||||
void test_filter_custom__initialize(void)
|
||||
{
|
||||
register_custom_filters();
|
||||
|
||||
g_repo = cl_git_sandbox_init("empty_standard_repo");
|
||||
|
||||
cl_git_mkfile(
|
||||
"empty_standard_repo/.gitattributes",
|
||||
"hero* bitflip reverse\n"
|
||||
"herofile text\n"
|
||||
"heroflip -reverse binary\n"
|
||||
"*.bin binary\n");
|
||||
}
|
||||
|
||||
void test_filter_custom__cleanup(void)
|
||||
{
|
||||
cl_git_sandbox_cleanup();
|
||||
g_repo = NULL;
|
||||
}
|
||||
|
||||
static int bitflip_filter_apply(
|
||||
git_filter *self,
|
||||
void **payload,
|
||||
git_buf *to,
|
||||
const git_buf *from,
|
||||
const git_filter_source *source)
|
||||
{
|
||||
const unsigned char *src = (const unsigned char *)from->ptr;
|
||||
unsigned char *dst;
|
||||
size_t i;
|
||||
|
||||
GIT_UNUSED(self); GIT_UNUSED(payload);
|
||||
|
||||
/* verify that attribute path match worked as expected */
|
||||
cl_assert_equal_i(
|
||||
0, git__strncmp("hero", git_filter_source_path(source), 4));
|
||||
|
||||
if (!from->size)
|
||||
return 0;
|
||||
|
||||
cl_git_pass(git_buf_grow(to, from->size));
|
||||
|
||||
dst = (unsigned char *)to->ptr;
|
||||
|
||||
for (i = 0; i < from->size; i++)
|
||||
dst[i] = VERY_SECURE_ENCRYPTION(src[i]);
|
||||
|
||||
to->size = from->size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bitflip_filter_free(git_filter *f)
|
||||
{
|
||||
git__free(f);
|
||||
}
|
||||
|
||||
static git_filter *create_bitflip_filter(void)
|
||||
{
|
||||
git_filter *filter = git__calloc(1, sizeof(git_filter));
|
||||
cl_assert(filter);
|
||||
|
||||
filter->version = GIT_FILTER_VERSION;
|
||||
filter->attributes = "+bitflip";
|
||||
filter->shutdown = bitflip_filter_free;
|
||||
filter->apply = bitflip_filter_apply;
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
||||
static int reverse_filter_apply(
|
||||
git_filter *self,
|
||||
void **payload,
|
||||
git_buf *to,
|
||||
const git_buf *from,
|
||||
const git_filter_source *source)
|
||||
{
|
||||
const unsigned char *src = (const unsigned char *)from->ptr;
|
||||
const unsigned char *end = src + from->size;
|
||||
unsigned char *dst;
|
||||
|
||||
GIT_UNUSED(self); GIT_UNUSED(payload); GIT_UNUSED(source);
|
||||
|
||||
/* verify that attribute path match worked as expected */
|
||||
cl_assert_equal_i(
|
||||
0, git__strncmp("hero", git_filter_source_path(source), 4));
|
||||
|
||||
if (!from->size)
|
||||
return 0;
|
||||
|
||||
cl_git_pass(git_buf_grow(to, from->size));
|
||||
|
||||
dst = (unsigned char *)to->ptr + from->size - 1;
|
||||
|
||||
while (src < end)
|
||||
*dst-- = *src++;
|
||||
|
||||
to->size = from->size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void reverse_filter_free(git_filter *f)
|
||||
{
|
||||
git__free(f);
|
||||
}
|
||||
|
||||
static git_filter *create_reverse_filter(const char *attrs)
|
||||
{
|
||||
git_filter *filter = git__calloc(1, sizeof(git_filter));
|
||||
cl_assert(filter);
|
||||
|
||||
filter->version = GIT_FILTER_VERSION;
|
||||
filter->attributes = attrs;
|
||||
filter->shutdown = reverse_filter_free;
|
||||
filter->apply = reverse_filter_apply;
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
static void register_custom_filters(void)
|
||||
{
|
||||
static int filters_registered = 0;
|
||||
|
||||
if (!filters_registered) {
|
||||
cl_git_pass(git_filter_register(
|
||||
"bitflip", create_bitflip_filter(), BITFLIP_FILTER_PRIORITY));
|
||||
|
||||
cl_git_pass(git_filter_register(
|
||||
"reverse", create_reverse_filter("+reverse"),
|
||||
REVERSE_FILTER_PRIORITY));
|
||||
|
||||
/* re-register reverse filter with standard filter=xyz priority */
|
||||
cl_git_pass(git_filter_register(
|
||||
"pre-reverse",
|
||||
create_reverse_filter("+prereverse"),
|
||||
GIT_FILTER_DRIVER_PRIORITY));
|
||||
|
||||
filters_registered = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void test_filter_custom__to_odb(void)
|
||||
{
|
||||
git_filter_list *fl;
|
||||
git_buf out = { 0 };
|
||||
git_buf in = GIT_BUF_INIT_CONST(workdir_data, strlen(workdir_data));
|
||||
|
||||
cl_git_pass(git_filter_list_load(
|
||||
&fl, g_repo, NULL, "herofile", GIT_FILTER_TO_ODB));
|
||||
|
||||
cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
|
||||
|
||||
cl_assert_equal_i(BITFLIPPED_AND_REVERSED_DATA_LEN, out.size);
|
||||
|
||||
cl_assert_equal_i(
|
||||
0, memcmp(bitflipped_and_reversed_data, out.ptr, out.size));
|
||||
|
||||
git_filter_list_free(fl);
|
||||
git_buf_free(&out);
|
||||
}
|
||||
|
||||
void test_filter_custom__to_workdir(void)
|
||||
{
|
||||
git_filter_list *fl;
|
||||
git_buf out = { 0 };
|
||||
git_buf in = GIT_BUF_INIT_CONST(
|
||||
bitflipped_and_reversed_data, BITFLIPPED_AND_REVERSED_DATA_LEN);
|
||||
|
||||
cl_git_pass(git_filter_list_load(
|
||||
&fl, g_repo, NULL, "herofile", GIT_FILTER_TO_WORKTREE));
|
||||
|
||||
cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
|
||||
|
||||
cl_assert_equal_i(strlen(workdir_data), out.size);
|
||||
|
||||
cl_assert_equal_i(
|
||||
0, memcmp(workdir_data, out.ptr, out.size));
|
||||
|
||||
git_filter_list_free(fl);
|
||||
git_buf_free(&out);
|
||||
}
|
||||
|
||||
void test_filter_custom__can_register_a_custom_filter_in_the_repository(void)
|
||||
{
|
||||
git_filter_list *fl;
|
||||
|
||||
cl_git_pass(git_filter_list_load(
|
||||
&fl, g_repo, NULL, "herofile", GIT_FILTER_TO_WORKTREE));
|
||||
/* expect: bitflip, reverse, crlf */
|
||||
cl_assert_equal_sz(3, git_filter_list_length(fl));
|
||||
git_filter_list_free(fl);
|
||||
|
||||
cl_git_pass(git_filter_list_load(
|
||||
&fl, g_repo, NULL, "herocorp", GIT_FILTER_TO_WORKTREE));
|
||||
/* expect: bitflip, reverse - possibly crlf depending on global config */
|
||||
{
|
||||
size_t flen = git_filter_list_length(fl);
|
||||
cl_assert(flen == 2 || flen == 3);
|
||||
}
|
||||
git_filter_list_free(fl);
|
||||
|
||||
cl_git_pass(git_filter_list_load(
|
||||
&fl, g_repo, NULL, "hero.bin", GIT_FILTER_TO_WORKTREE));
|
||||
/* expect: bitflip, reverse */
|
||||
cl_assert_equal_sz(2, git_filter_list_length(fl));
|
||||
git_filter_list_free(fl);
|
||||
|
||||
cl_git_pass(git_filter_list_load(
|
||||
&fl, g_repo, NULL, "heroflip", GIT_FILTER_TO_WORKTREE));
|
||||
/* expect: bitflip (because of -reverse) */
|
||||
cl_assert_equal_sz(1, git_filter_list_length(fl));
|
||||
git_filter_list_free(fl);
|
||||
|
||||
cl_git_pass(git_filter_list_load(
|
||||
&fl, g_repo, NULL, "doesntapplytome.bin", GIT_FILTER_TO_WORKTREE));
|
||||
/* expect: none */
|
||||
cl_assert_equal_sz(0, git_filter_list_length(fl));
|
||||
git_filter_list_free(fl);
|
||||
}
|
||||
|
||||
void test_filter_custom__order_dependency(void)
|
||||
{
|
||||
git_index *index;
|
||||
git_blob *blob;
|
||||
git_buf buf = { 0 };
|
||||
|
||||
/* so if ident and reverse are used together, an interesting thing
|
||||
* happens - a reversed "$Id$" string is no longer going to trigger
|
||||
* ident correctly. When checking out, the filters should be applied
|
||||
* in order CLRF, then ident, then reverse, so ident expansion should
|
||||
* work correctly. On check in, the content should be reversed, then
|
||||
* ident, then CRLF filtered. Let's make sure that works...
|
||||
*/
|
||||
|
||||
cl_git_mkfile(
|
||||
"empty_standard_repo/.gitattributes",
|
||||
"hero.*.rev-ident text ident prereverse eol=lf\n");
|
||||
|
||||
cl_git_mkfile(
|
||||
"empty_standard_repo/hero.1.rev-ident",
|
||||
"This is a test\n$Id$\nHave fun!\n");
|
||||
|
||||
cl_git_mkfile(
|
||||
"empty_standard_repo/hero.2.rev-ident",
|
||||
"Another test\n$dI$\nCrazy!\n");
|
||||
|
||||
cl_git_pass(git_repository_index(&index, g_repo));
|
||||
cl_git_pass(git_index_add_bypath(index, "hero.1.rev-ident"));
|
||||
cl_git_pass(git_index_add_bypath(index, "hero.2.rev-ident"));
|
||||
cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "Filter chains\n");
|
||||
git_index_free(index);
|
||||
|
||||
cl_git_pass(git_blob_lookup(&blob, g_repo,
|
||||
& git_index_get_bypath(index, "hero.1.rev-ident", 0)->oid));
|
||||
cl_assert_equal_s(
|
||||
"\n!nuf evaH\n$dI$\ntset a si sihT", git_blob_rawcontent(blob));
|
||||
cl_git_pass(git_blob_filtered_content(&buf, blob, "hero.1.rev-ident", 0));
|
||||
/* no expansion because id was reversed at checkin and now at ident
|
||||
* time, reverse is not applied yet */
|
||||
cl_assert_equal_s(
|
||||
"This is a test\n$Id$\nHave fun!\n", buf.ptr);
|
||||
git_blob_free(blob);
|
||||
|
||||
cl_git_pass(git_blob_lookup(&blob, g_repo,
|
||||
& git_index_get_bypath(index, "hero.2.rev-ident", 0)->oid));
|
||||
cl_assert_equal_s(
|
||||
"\n!yzarC\n$Id$\ntset rehtonA", git_blob_rawcontent(blob));
|
||||
cl_git_pass(git_blob_filtered_content(&buf, blob, "hero.2.rev-ident", 0));
|
||||
/* expansion because reverse was applied at checkin and at ident time,
|
||||
* reverse is not applied yet */
|
||||
cl_assert_equal_s(
|
||||
"Another test\n$59001fe193103b1016b27027c0c827d036fd0ac8 :dI$\nCrazy!\n", buf.ptr);
|
||||
cl_assert_equal_i(0, git_oid_strcmp(
|
||||
git_blob_id(blob), "8ca0df630d728c0c72072b6101b301391ef10095"));
|
||||
git_blob_free(blob);
|
||||
|
||||
git_buf_free(&buf);
|
||||
}
|
||||
|
||||
void test_filter_custom__filter_registry_failure_cases(void)
|
||||
{
|
||||
git_filter fake = { GIT_FILTER_VERSION, 0 };
|
||||
|
||||
cl_assert_equal_i(GIT_EEXISTS, git_filter_register("bitflip", &fake, 0));
|
||||
|
||||
cl_git_fail(git_filter_unregister(GIT_FILTER_CRLF));
|
||||
cl_git_fail(git_filter_unregister(GIT_FILTER_IDENT));
|
||||
cl_assert_equal_i(GIT_ENOTFOUND, git_filter_unregister("not-a-filter"));
|
||||
}
|
||||
131
tests-clar/filter/ident.c
Normal file
131
tests-clar/filter/ident.c
Normal file
@ -0,0 +1,131 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "git2/sys/filter.h"
|
||||
|
||||
static git_repository *g_repo = NULL;
|
||||
|
||||
void test_filter_ident__initialize(void)
|
||||
{
|
||||
g_repo = cl_git_sandbox_init("crlf");
|
||||
}
|
||||
|
||||
void test_filter_ident__cleanup(void)
|
||||
{
|
||||
cl_git_sandbox_cleanup();
|
||||
}
|
||||
|
||||
static void add_blob_and_filter(
|
||||
const char *data,
|
||||
git_filter_list *fl,
|
||||
const char *expected)
|
||||
{
|
||||
git_oid id;
|
||||
git_blob *blob;
|
||||
git_buf out = { 0 };
|
||||
|
||||
cl_git_mkfile("crlf/identtest", data);
|
||||
cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "identtest"));
|
||||
cl_git_pass(git_blob_lookup(&blob, g_repo, &id));
|
||||
|
||||
cl_git_pass(git_filter_list_apply_to_blob(&out, fl, blob));
|
||||
|
||||
cl_assert_equal_s(expected, out.ptr);
|
||||
|
||||
git_blob_free(blob);
|
||||
git_buf_free(&out);
|
||||
}
|
||||
|
||||
void test_filter_ident__to_worktree(void)
|
||||
{
|
||||
git_filter_list *fl;
|
||||
git_filter *ident;
|
||||
|
||||
cl_git_pass(git_filter_list_new(&fl, g_repo, GIT_FILTER_TO_WORKTREE));
|
||||
|
||||
ident = git_filter_lookup(GIT_FILTER_IDENT);
|
||||
cl_assert(ident != NULL);
|
||||
|
||||
cl_git_pass(git_filter_list_push(fl, ident, NULL));
|
||||
|
||||
add_blob_and_filter(
|
||||
"Hello\n$Id$\nFun stuff\n", fl,
|
||||
"Hello\n$Id: b69e2387aafcaf73c4de5b9ab59abe27fdadee30$\nFun stuff\n");
|
||||
add_blob_and_filter(
|
||||
"Hello\n$Id: Junky$\nFun stuff\n", fl,
|
||||
"Hello\n$Id: 45cd107a7102911cb2a7df08404674327fa050b9$\nFun stuff\n");
|
||||
add_blob_and_filter(
|
||||
"$Id$\nAt the start\n", fl,
|
||||
"$Id: b13415c767abc196fb95bd17070e8c1113e32160$\nAt the start\n");
|
||||
add_blob_and_filter(
|
||||
"At the end\n$Id$", fl,
|
||||
"At the end\n$Id: 1344925c6bc65b34c5a7b50f86bf688e48e9a272$");
|
||||
add_blob_and_filter(
|
||||
"$Id$", fl,
|
||||
"$Id: b3f5ebfb5843bc43ceecff6d4f26bb37c615beb1$");
|
||||
add_blob_and_filter(
|
||||
"$Id: Some sort of junk goes here$", fl,
|
||||
"$Id: ab2dd3853c7c9a4bff55aca2bea077a73c32ac06$");
|
||||
|
||||
add_blob_and_filter("$Id: ", fl, "$Id: ");
|
||||
add_blob_and_filter("$Id", fl, "$Id");
|
||||
add_blob_and_filter("$I", fl, "$I");
|
||||
add_blob_and_filter("Id$", fl, "Id$");
|
||||
|
||||
git_filter_list_free(fl);
|
||||
}
|
||||
|
||||
void test_filter_ident__to_odb(void)
|
||||
{
|
||||
git_filter_list *fl;
|
||||
git_filter *ident;
|
||||
|
||||
cl_git_pass(git_filter_list_new(&fl, g_repo, GIT_FILTER_TO_ODB));
|
||||
|
||||
ident = git_filter_lookup(GIT_FILTER_IDENT);
|
||||
cl_assert(ident != NULL);
|
||||
|
||||
cl_git_pass(git_filter_list_push(fl, ident, NULL));
|
||||
|
||||
add_blob_and_filter(
|
||||
"Hello\n$Id$\nFun stuff\n",
|
||||
fl, "Hello\n$Id$\nFun stuff\n");
|
||||
add_blob_and_filter(
|
||||
"Hello\n$Id: b69e2387aafcaf73c4de5b9ab59abe27fdadee30$\nFun stuff\n",
|
||||
fl, "Hello\n$Id$\nFun stuff\n");
|
||||
add_blob_and_filter(
|
||||
"Hello\n$Id: Any junk you may have left here$\nFun stuff\n",
|
||||
fl, "Hello\n$Id$\nFun stuff\n");
|
||||
add_blob_and_filter(
|
||||
"Hello\n$Id:$\nFun stuff\n",
|
||||
fl, "Hello\n$Id$\nFun stuff\n");
|
||||
add_blob_and_filter(
|
||||
"Hello\n$Id:x$\nFun stuff\n",
|
||||
fl, "Hello\n$Id$\nFun stuff\n");
|
||||
|
||||
add_blob_and_filter(
|
||||
"$Id$\nAt the start\n", fl, "$Id$\nAt the start\n");
|
||||
add_blob_and_filter(
|
||||
"$Id: lots of random text that should be removed from here$\nAt the start\n", fl, "$Id$\nAt the start\n");
|
||||
add_blob_and_filter(
|
||||
"$Id: lots of random text that should not be removed without a terminator\nAt the start\n", fl, "$Id: lots of random text that should not be removed without a terminator\nAt the start\n");
|
||||
|
||||
add_blob_and_filter(
|
||||
"At the end\n$Id$", fl, "At the end\n$Id$");
|
||||
add_blob_and_filter(
|
||||
"At the end\n$Id:$", fl, "At the end\n$Id$");
|
||||
add_blob_and_filter(
|
||||
"At the end\n$Id:asdfasdf$", fl, "At the end\n$Id$");
|
||||
add_blob_and_filter(
|
||||
"At the end\n$Id", fl, "At the end\n$Id");
|
||||
add_blob_and_filter(
|
||||
"At the end\n$IddI", fl, "At the end\n$IddI");
|
||||
|
||||
add_blob_and_filter("$Id$", fl, "$Id$");
|
||||
add_blob_and_filter("$Id: any$", fl, "$Id$");
|
||||
add_blob_and_filter("$Id: any long stuff goes here you see$", fl, "$Id$");
|
||||
add_blob_and_filter("$Id: ", fl, "$Id: ");
|
||||
add_blob_and_filter("$Id", fl, "$Id");
|
||||
add_blob_and_filter("$I", fl, "$I");
|
||||
add_blob_and_filter("Id$", fl, "Id$");
|
||||
|
||||
git_filter_list_free(fl);
|
||||
}
|
||||
@ -120,37 +120,6 @@ static void check_stat_data(git_index *index, const char *path, bool match)
|
||||
}
|
||||
}
|
||||
|
||||
static void commit_index_to_head(
|
||||
git_repository *repo,
|
||||
const char *commit_message)
|
||||
{
|
||||
git_index *index;
|
||||
git_oid tree_id, commit_id;
|
||||
git_tree *tree;
|
||||
git_signature *sig;
|
||||
git_commit *parent = NULL;
|
||||
|
||||
git_revparse_single((git_object **)&parent, repo, "HEAD");
|
||||
/* it is okay if looking up the HEAD fails */
|
||||
|
||||
cl_git_pass(git_repository_index(&index, repo));
|
||||
cl_git_pass(git_index_write_tree(&tree_id, index));
|
||||
cl_git_pass(git_index_write(index)); /* not needed, but might as well */
|
||||
git_index_free(index);
|
||||
|
||||
cl_git_pass(git_tree_lookup(&tree, repo, &tree_id));
|
||||
|
||||
cl_git_pass(git_signature_now(&sig, "Testy McTester", "tt@tester.test"));
|
||||
|
||||
cl_git_pass(git_commit_create_v(
|
||||
&commit_id, repo, "HEAD", sig, sig,
|
||||
NULL, commit_message, tree, parent ? 1 : 0, parent));
|
||||
|
||||
git_commit_free(parent);
|
||||
git_tree_free(tree);
|
||||
git_signature_free(sig);
|
||||
}
|
||||
|
||||
void test_index_addall__repo_lifecycle(void)
|
||||
{
|
||||
int error;
|
||||
@ -197,7 +166,7 @@ void test_index_addall__repo_lifecycle(void)
|
||||
check_stat_data(index, "addall/file.zzz", true);
|
||||
check_status(g_repo, 2, 0, 0, 3, 0, 0, 1);
|
||||
|
||||
commit_index_to_head(g_repo, "first commit");
|
||||
cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "first commit");
|
||||
check_status(g_repo, 0, 0, 0, 3, 0, 0, 1);
|
||||
|
||||
/* attempt to add an ignored file - does nothing */
|
||||
@ -244,7 +213,7 @@ void test_index_addall__repo_lifecycle(void)
|
||||
cl_git_pass(git_index_add_bypath(index, "file.zzz"));
|
||||
check_status(g_repo, 1, 0, 1, 3, 0, 0, 0);
|
||||
|
||||
commit_index_to_head(g_repo, "second commit");
|
||||
cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "second commit");
|
||||
check_status(g_repo, 0, 0, 0, 3, 0, 0, 0);
|
||||
|
||||
cl_must_pass(p_unlink("addall/file.zzz"));
|
||||
|
||||
@ -44,7 +44,8 @@ static void replace_file_with_mode(
|
||||
|
||||
cl_git_pass(p_rename(path.ptr, backup));
|
||||
cl_git_write2file(
|
||||
path.ptr, content.ptr, O_WRONLY|O_CREAT|O_TRUNC, create_mode);
|
||||
path.ptr, content.ptr, content.size,
|
||||
O_WRONLY|O_CREAT|O_TRUNC, create_mode);
|
||||
|
||||
git_buf_free(&path);
|
||||
git_buf_free(&content);
|
||||
@ -91,7 +92,7 @@ void test_index_filemodes__untrusted(void)
|
||||
add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE);
|
||||
|
||||
/* 5 - add new 0644 -> expect 0644 */
|
||||
cl_git_write2file("filemodes/new_off", "blah",
|
||||
cl_git_write2file("filemodes/new_off", "blah", 0,
|
||||
O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB);
|
||||
|
||||
@ -100,7 +101,7 @@ void test_index_filemodes__untrusted(void)
|
||||
*/
|
||||
if (can_filemode) {
|
||||
/* 6 - add 0755 -> expect 0755 */
|
||||
cl_git_write2file("filemodes/new_on", "blah",
|
||||
cl_git_write2file("filemodes/new_on", "blah", 0,
|
||||
O_WRONLY | O_CREAT | O_TRUNC, 0755);
|
||||
add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE);
|
||||
}
|
||||
@ -140,12 +141,12 @@ void test_index_filemodes__trusted(void)
|
||||
add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE);
|
||||
|
||||
/* 5 - add new 0644 -> expect 0644 */
|
||||
cl_git_write2file("filemodes/new_off", "blah",
|
||||
cl_git_write2file("filemodes/new_off", "blah", 0,
|
||||
O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB);
|
||||
|
||||
/* 6 - add 0755 -> expect 0755 */
|
||||
cl_git_write2file("filemodes/new_on", "blah",
|
||||
cl_git_write2file("filemodes/new_on", "blah", 0,
|
||||
O_WRONLY | O_CREAT | O_TRUNC, 0755);
|
||||
add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE);
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "posix.h"
|
||||
#include "blob.h"
|
||||
#include "filter.h"
|
||||
#include "buf_text.h"
|
||||
|
||||
static git_repository *g_repo = NULL;
|
||||
#define NUM_TEST_OBJECTS 9
|
||||
static git_oid g_oids[NUM_TEST_OBJECTS];
|
||||
static const char *g_raw[NUM_TEST_OBJECTS] = {
|
||||
|
||||
#define CRLF_NUM_TEST_OBJECTS 9
|
||||
|
||||
static const char *g_crlf_raw[CRLF_NUM_TEST_OBJECTS] = {
|
||||
"",
|
||||
"foo\nbar\n",
|
||||
"foo\rbar\r",
|
||||
@ -18,19 +18,14 @@ static const char *g_raw[NUM_TEST_OBJECTS] = {
|
||||
"\xEF\xBB\xBF\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\r\n\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\r\n",
|
||||
"\xFE\xFF\x00T\x00h\x00i\x00s\x00!"
|
||||
};
|
||||
static git_off_t g_len[NUM_TEST_OBJECTS] = { -1, -1, -1, -1, -1, 17, -1, -1, 12 };
|
||||
static git_buf_text_stats g_stats[NUM_TEST_OBJECTS] = {
|
||||
{ 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 2, 0, 6, 0 },
|
||||
{ 0, 0, 2, 0, 0, 6, 0 },
|
||||
{ 0, 0, 2, 2, 2, 6, 0 },
|
||||
{ 0, 0, 4, 4, 1, 31, 0 },
|
||||
{ 0, 1, 1, 2, 1, 9, 5 },
|
||||
{ GIT_BOM_UTF8, 0, 0, 1, 0, 16, 0 },
|
||||
{ GIT_BOM_UTF8, 0, 2, 2, 2, 27, 0 },
|
||||
{ GIT_BOM_UTF16_BE, 5, 0, 0, 0, 7, 5 },
|
||||
|
||||
static git_off_t g_crlf_raw_len[CRLF_NUM_TEST_OBJECTS] = {
|
||||
-1, -1, -1, -1, -1, 17, -1, -1, 12
|
||||
};
|
||||
static git_buf g_crlf_filtered[NUM_TEST_OBJECTS] = {
|
||||
|
||||
static git_oid g_crlf_oids[CRLF_NUM_TEST_OBJECTS];
|
||||
|
||||
static git_buf g_crlf_filtered[CRLF_NUM_TEST_OBJECTS] = {
|
||||
{ "", 0, 0 },
|
||||
{ "foo\nbar\n", 0, 8 },
|
||||
{ "foo\rbar\r", 0, 8 },
|
||||
@ -42,30 +37,36 @@ static git_buf g_crlf_filtered[NUM_TEST_OBJECTS] = {
|
||||
{ "\xFE\xFF\x00T\x00h\x00i\x00s\x00!", 0, 12 }
|
||||
};
|
||||
|
||||
static git_buf_text_stats g_crlf_filtered_stats[CRLF_NUM_TEST_OBJECTS] = {
|
||||
{ 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 2, 0, 6, 0 },
|
||||
{ 0, 0, 2, 0, 0, 6, 0 },
|
||||
{ 0, 0, 2, 2, 2, 6, 0 },
|
||||
{ 0, 0, 4, 4, 1, 31, 0 },
|
||||
{ 0, 1, 1, 2, 1, 9, 5 },
|
||||
{ GIT_BOM_UTF8, 0, 0, 1, 0, 16, 0 },
|
||||
{ GIT_BOM_UTF8, 0, 2, 2, 2, 27, 0 },
|
||||
{ GIT_BOM_UTF16_BE, 5, 0, 0, 0, 7, 5 },
|
||||
};
|
||||
|
||||
void test_object_blob_filter__initialize(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
cl_fixture_sandbox("empty_standard_repo");
|
||||
cl_git_pass(p_rename(
|
||||
"empty_standard_repo/.gitted", "empty_standard_repo/.git"));
|
||||
cl_git_pass(git_repository_open(&g_repo, "empty_standard_repo"));
|
||||
g_repo = cl_git_sandbox_init("empty_standard_repo");
|
||||
|
||||
for (i = 0; i < NUM_TEST_OBJECTS; i++) {
|
||||
size_t len = (g_len[i] < 0) ? strlen(g_raw[i]) : (size_t)g_len[i];
|
||||
g_len[i] = (git_off_t)len;
|
||||
for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) {
|
||||
if (g_crlf_raw_len[i] < 0)
|
||||
g_crlf_raw_len[i] = strlen(g_crlf_raw[i]);
|
||||
|
||||
cl_git_pass(
|
||||
git_blob_create_frombuffer(&g_oids[i], g_repo, g_raw[i], len)
|
||||
);
|
||||
cl_git_pass(git_blob_create_frombuffer(
|
||||
&g_crlf_oids[i], g_repo, g_crlf_raw[i], (size_t)g_crlf_raw_len[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void test_object_blob_filter__cleanup(void)
|
||||
{
|
||||
git_repository_free(g_repo);
|
||||
g_repo = NULL;
|
||||
cl_fixture_cleanup("empty_standard_repo");
|
||||
cl_git_sandbox_cleanup();
|
||||
}
|
||||
|
||||
void test_object_blob_filter__unfiltered(void)
|
||||
@ -73,10 +74,15 @@ void test_object_blob_filter__unfiltered(void)
|
||||
int i;
|
||||
git_blob *blob;
|
||||
|
||||
for (i = 0; i < NUM_TEST_OBJECTS; i++) {
|
||||
cl_git_pass(git_blob_lookup(&blob, g_repo, &g_oids[i]));
|
||||
cl_assert(g_len[i] == git_blob_rawsize(blob));
|
||||
cl_assert(memcmp(git_blob_rawcontent(blob), g_raw[i], (size_t)g_len[i]) == 0);
|
||||
for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) {
|
||||
size_t raw_len = (size_t)g_crlf_raw_len[i];
|
||||
|
||||
cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i]));
|
||||
|
||||
cl_assert_equal_sz(raw_len, (size_t)git_blob_rawsize(blob));
|
||||
cl_assert_equal_i(
|
||||
0, memcmp(g_crlf_raw[i], git_blob_rawcontent(blob), raw_len));
|
||||
|
||||
git_blob_free(blob);
|
||||
}
|
||||
}
|
||||
@ -88,11 +94,12 @@ void test_object_blob_filter__stats(void)
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
git_buf_text_stats stats;
|
||||
|
||||
for (i = 0; i < NUM_TEST_OBJECTS; i++) {
|
||||
cl_git_pass(git_blob_lookup(&blob, g_repo, &g_oids[i]));
|
||||
for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) {
|
||||
cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i]));
|
||||
cl_git_pass(git_blob__getbuf(&buf, blob));
|
||||
git_buf_text_gather_stats(&stats, &buf, false);
|
||||
cl_assert(memcmp(&g_stats[i], &stats, sizeof(stats)) == 0);
|
||||
cl_assert_equal_i(
|
||||
0, memcmp(&g_crlf_filtered_stats[i], &stats, sizeof(stats)));
|
||||
git_blob_free(blob);
|
||||
}
|
||||
|
||||
@ -101,11 +108,11 @@ void test_object_blob_filter__stats(void)
|
||||
|
||||
void test_object_blob_filter__to_odb(void)
|
||||
{
|
||||
git_vector filters = GIT_VECTOR_INIT;
|
||||
git_filter_list *fl = NULL;
|
||||
git_config *cfg;
|
||||
int i;
|
||||
git_blob *blob;
|
||||
git_buf orig = GIT_BUF_INIT, out = GIT_BUF_INIT;
|
||||
git_buf out = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_repository_config(&cfg, g_repo));
|
||||
cl_assert(cfg);
|
||||
@ -113,23 +120,24 @@ void test_object_blob_filter__to_odb(void)
|
||||
git_attr_cache_flush(g_repo);
|
||||
cl_git_append2file("empty_standard_repo/.gitattributes", "*.txt text\n");
|
||||
|
||||
cl_assert(git_filters_load(
|
||||
&filters, g_repo, "filename.txt", GIT_FILTER_TO_ODB) > 0);
|
||||
cl_assert(filters.length == 1);
|
||||
cl_git_pass(git_filter_list_load(
|
||||
&fl, g_repo, NULL, "filename.txt", GIT_FILTER_TO_ODB));
|
||||
cl_assert(fl != NULL);
|
||||
|
||||
for (i = 0; i < NUM_TEST_OBJECTS; i++) {
|
||||
cl_git_pass(git_blob_lookup(&blob, g_repo, &g_oids[i]));
|
||||
cl_git_pass(git_blob__getbuf(&orig, blob));
|
||||
for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) {
|
||||
cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i]));
|
||||
|
||||
cl_git_pass(git_filters_apply(&out, &orig, &filters));
|
||||
cl_assert(git_buf_cmp(&out, &g_crlf_filtered[i]) == 0);
|
||||
cl_git_pass(git_filter_list_apply_to_blob(&out, fl, blob));
|
||||
|
||||
cl_assert_equal_sz(g_crlf_filtered[i].size, out.size);
|
||||
|
||||
cl_assert_equal_i(
|
||||
0, memcmp(out.ptr, g_crlf_filtered[i].ptr, out.size));
|
||||
|
||||
git_blob_free(blob);
|
||||
}
|
||||
|
||||
git_filters_free(&filters);
|
||||
git_buf_free(&orig);
|
||||
git_filter_list_free(fl);
|
||||
git_buf_free(&out);
|
||||
git_config_free(cfg);
|
||||
}
|
||||
|
||||
|
||||
@ -69,7 +69,7 @@ void test_online_clone__empty_repository(void)
|
||||
cl_git_pass(git_clone(&g_repo, LIVE_EMPTYREPO_URL, "./foo", &g_options));
|
||||
|
||||
cl_assert_equal_i(true, git_repository_is_empty(g_repo));
|
||||
cl_assert_equal_i(true, git_repository_head_orphan(g_repo));
|
||||
cl_assert_equal_i(true, git_repository_head_unborn(g_repo));
|
||||
|
||||
cl_git_pass(git_reference_lookup(&head, g_repo, GIT_HEAD_FILE));
|
||||
cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head));
|
||||
|
||||
@ -64,6 +64,11 @@ void test_online_fetch__default_http(void)
|
||||
do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6);
|
||||
}
|
||||
|
||||
void test_online_fetch__default_https(void)
|
||||
{
|
||||
do_fetch("https://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6);
|
||||
}
|
||||
|
||||
void test_online_fetch__no_tags_git(void)
|
||||
{
|
||||
do_fetch("git://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3);
|
||||
|
||||
@ -57,11 +57,11 @@ void test_refs_branches_delete__can_delete_a_branch_even_if_HEAD_is_missing(void
|
||||
git_reference_free(branch);
|
||||
}
|
||||
|
||||
void test_refs_branches_delete__can_delete_a_branch_when_HEAD_is_orphaned(void)
|
||||
void test_refs_branches_delete__can_delete_a_branch_when_HEAD_is_unborn(void)
|
||||
{
|
||||
git_reference *branch;
|
||||
|
||||
make_head_orphaned(repo, NON_EXISTING_HEAD);
|
||||
make_head_unborn(repo, NON_EXISTING_HEAD);
|
||||
|
||||
cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL));
|
||||
cl_git_pass(git_branch_delete(branch));
|
||||
|
||||
@ -26,13 +26,13 @@ void test_refs_branches_ishead__can_tell_if_a_branch_is_pointed_at_by_HEAD(void)
|
||||
cl_assert_equal_i(true, git_branch_is_head(branch));
|
||||
}
|
||||
|
||||
void test_refs_branches_ishead__can_properly_handle_orphaned_HEAD(void)
|
||||
void test_refs_branches_ishead__can_properly_handle_unborn_HEAD(void)
|
||||
{
|
||||
git_repository_free(repo);
|
||||
|
||||
repo = cl_git_sandbox_init("testrepo.git");
|
||||
|
||||
make_head_orphaned(repo, NON_EXISTING_HEAD);
|
||||
make_head_unborn(repo, NON_EXISTING_HEAD);
|
||||
|
||||
cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master"));
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user