Unverified Commit 1e5b1395 by Edward Thomson Committed by GitHub

Merge pull request #5467 from pks-t/pks/v0.28.5

Release v0.28.5
parents 106a5f27 635dc2ff
......@@ -23,6 +23,7 @@ Dmitry Kovega
Emeric Fermas
Emmanuel Rodriguez
Eric Myhre
Erik Aigner
Florian Forster
Holger Weiss
Ingmar Vanhassel
......
......@@ -61,7 +61,7 @@ jobs:
- job: macos
displayName: 'macOS'
pool:
vmImage: 'macOS 10.13'
vmImage: 'macOS-10.14'
steps:
- bash: . '$(Build.SourcesDirectory)/ci/setup-osx.sh'
displayName: Setup
......@@ -76,25 +76,28 @@ jobs:
- job: windows_vs_amd64
displayName: 'Windows (amd64; Visual Studio)'
pool: Hosted
pool:
vmImage: 'vs2017-win2016'
steps:
- template: azure-pipelines/powershell.yml
parameters:
environmentVariables:
CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 12 2013 Win64" -DDEPRECATE_HARD=ON
CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 15 2017" -A x64 -DDEPRECATE_HARD=ON
- job: windows_vs_x86
displayName: 'Windows (x86; Visual Studio)'
pool: Hosted
pool:
vmImage: 'vs2017-win2016'
steps:
- template: azure-pipelines/powershell.yml
parameters:
environmentVariables:
CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 12 2013" -DDEPRECATE_HARD=ON
CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 15 2017" -A Win32 -DDEPRECATE_HARD=ON
- job: windows_mingw_amd64
displayName: 'Windows (amd64; MinGW)'
pool: Hosted
pool:
vmImage: 'vs2017-win2016'
steps:
- powershell: . '$(Build.SourcesDirectory)\ci\setup-mingw.ps1'
displayName: Setup
......@@ -104,12 +107,13 @@ jobs:
- template: azure-pipelines/powershell.yml
parameters:
environmentVariables:
BUILD_PATH: $(Agent.TempDirectory)\mingw64\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin
CMAKE_OPTIONS: -G"MinGW Makefiles" -DDEPRECATE_HARD=ON
PATH: $(Agent.TempDirectory)\mingw64\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin
- job: windows_mingw_x86
displayName: 'Windows (x86; MinGW)'
pool: Hosted
pool:
vmImage: 'vs2017-win2016'
steps:
- powershell: . '$(Build.SourcesDirectory)\ci\setup-mingw.ps1'
displayName: Setup
......@@ -120,8 +124,8 @@ jobs:
- template: azure-pipelines/powershell.yml
parameters:
environmentVariables:
BUILD_PATH: $(Agent.TempDirectory)\mingw32\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin
CMAKE_OPTIONS: -G"MinGW Makefiles" -DDEPRECATE_HARD=ON
PATH: $(Agent.TempDirectory)\mingw32\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin
- job: documentation
displayName: 'Generate Documentation'
......
......@@ -61,7 +61,7 @@ jobs:
- job: macos
displayName: 'macOS'
pool:
vmImage: 'macOS 10.13'
vmImage: 'macOS-10.14'
steps:
- bash: . '$(Build.SourcesDirectory)/ci/setup-osx.sh'
displayName: Setup
......@@ -77,27 +77,30 @@ jobs:
- job: windows_vs_amd64
displayName: 'Windows (amd64; Visual Studio)'
pool: Hosted
pool:
vmImage: 'vs2017-win2016'
steps:
- template: powershell.yml
parameters:
environmentVariables:
CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 12 2013 Win64" -DDEPRECATE_HARD=ON
CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 15 2017" -A x64 -DDEPRECATE_HARD=ON
RUN_INVASIVE_TESTS: true
- job: windows_vs_x86
displayName: 'Windows (x86; Visual Studio)'
pool: Hosted
pool:
vmImage: 'vs2017-win2016'
steps:
- template: powershell.yml
parameters:
environmentVariables:
CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 12 2013" -DDEPRECATE_HARD=ON
CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 15 2017" -A Win32 -DDEPRECATE_HARD=ON
RUN_INVASIVE_TESTS: true
- job: windows_mingw_amd64
displayName: 'Windows (amd64; MinGW)'
pool: Hosted
pool:
vmImage: 'vs2017-win2016'
steps:
- powershell: . '$(Build.SourcesDirectory)\ci\setup-mingw.ps1'
displayName: Setup
......@@ -107,13 +110,14 @@ jobs:
- template: powershell.yml
parameters:
environmentVariables:
BUILD_PATH: $(Agent.TempDirectory)\mingw64\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin
CMAKE_OPTIONS: -G"MinGW Makefiles" -DDEPRECATE_HARD=ON
PATH: $(Agent.TempDirectory)\mingw64\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin
RUN_INVASIVE_TESTS: true
- job: windows_mingw_x86
displayName: 'Windows (x86; MinGW)'
pool: Hosted
pool:
vmImage: 'vs2017-win2016'
steps:
- powershell: . '$(Build.SourcesDirectory)\ci\setup-mingw.ps1'
displayName: Setup
......@@ -124,8 +128,8 @@ jobs:
- template: powershell.yml
parameters:
environmentVariables:
BUILD_PATH: $(Agent.TempDirectory)\mingw32\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin
CMAKE_OPTIONS: -G"MinGW Makefiles" -DDEPRECATE_HARD=ON
PATH: $(Agent.TempDirectory)\mingw32\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin
RUN_INVASIVE_TESTS: true
- job: linux_x86_bionic_gcc_openssl
......
......@@ -6,6 +6,12 @@ $PSDefaultParameterValues['*:ErrorAction'] = 'Stop'
if ($Env:SOURCE_DIR) { $SourceDirectory = $Env:SOURCE_DIR } else { $SourceDirectory = Split-Path (Split-Path $MyInvocation.MyCommand.Path -Parent) -Parent }
$BuildDirectory = $(Get-Location).Path
# Resolve CMake before modifying PATH
$CMAKE = (Get-Command cmake).Path
# Prepend BUILD_PATH to make available our own tools
if ($Env:BUILD_PATH) { $Env:PATH = $Env:BUILD_PATH }
Write-Host "Source directory: ${SourceDirectory}"
Write-Host "Build directory: ${BuildDirectory}"
Write-Host ""
......@@ -18,7 +24,7 @@ Write-Host "####################################################################
Write-Host "## Configuring build environment"
Write-Host "##############################################################################"
Invoke-Expression "cmake ${SourceDirectory} -DBUILD_EXAMPLES=ON ${Env:CMAKE_OPTIONS}"
Invoke-Expression "& '${CMAKE}' ${SourceDirectory} -DBUILD_EXAMPLES=ON ${Env:CMAKE_OPTIONS}"
if ($LastExitCode -ne 0) { [Environment]::Exit($LastExitCode) }
Write-Host ""
......@@ -26,5 +32,5 @@ Write-Host "####################################################################
Write-Host "## Building libgit2"
Write-Host "##############################################################################"
cmake --build .
Invoke-Expression "& '${CMAKE}' --build ."
if ($LastExitCode -ne 0) { [Environment]::Exit($LastExitCode) }
......@@ -29,8 +29,32 @@ function run_test {
$TestCommand = (ctest -N -V -R "^$TestName$") -join "`n" -replace "(?ms).*\n^[0-9]*: Test command: ","" -replace "\n.*",""
$TestCommand += " -r${BuildDir}\results_${TestName}.xml"
Invoke-Expression $TestCommand
if ($LastExitCode -ne 0) { $global:Success = $false }
if ($Env:GITTEST_FLAKY_RETRY -gt 0) {
$AttemptsRemain = $Env:GITTEST_FLAKY_RETRY
} else {
$AttemptsRemain = 1
}
$Failed = 0
while ($AttemptsRemain -ne 0) {
if ($Failed -eq 1) {
Write-Host ""
Write-Host "Re-running flaky $TestName tests..."
Write-Host ""
}
Invoke-Expression $TestCommand
if ($LastExitCode -eq 0) {
$Failed = 0
break
} else {
$Failed = 1
}
$AttemptsRemain = $AttemptsRemain - 1
}
if ($Failed -eq 1) { $global:Success = $false }
}
Write-Host "##############################################################################"
......@@ -72,7 +96,9 @@ if (-not $Env:SKIP_ONLINE_TESTS) {
Write-Host "## Running (online) tests"
Write-Host "##############################################################################"
$Env:GITTEST_FLAKY_RETRY=5
run_test online
$Env:GITTEST_FLAKY_RETRY=0
}
if (-not $Env:SKIP_PROXY_TESTS) {
......
......@@ -32,11 +32,6 @@ cleanup() {
echo "Done."
}
failure() {
echo "Test exited with code: $1"
SUCCESS=0
}
# Ask ctest what it would run if we were to invoke it directly. This lets
# us manage the test configuration in a single place (tests/CMakeLists.txt)
# instead of running clar here as well. But it allows us to wrap our test
......@@ -60,7 +55,35 @@ run_test() {
RUNNER="$TEST_CMD"
fi
eval $RUNNER || failure
if [[ "$GITTEST_FLAKY_RETRY" > 0 ]]; then
ATTEMPTS_REMAIN=$GITTEST_FLAKY_RETRY
else
ATTEMPTS_REMAIN=1
fi
FAILED=0
while [[ "$ATTEMPTS_REMAIN" > 0 ]]; do
if [ "$FAILED" -eq 1 ]; then
echo ""
echo "Re-running flaky ${1} tests..."
echo ""
fi
RETURN_CODE=0
eval $RUNNER || RETURN_CODE=$? && true
if [ "$RETURN_CODE" -eq 0 ]; then
break
fi
echo "Test exited with code: $RETURN_CODE"
ATTEMPTS_REMAIN="$(($ATTEMPTS_REMAIN-1))"
FAILED=1
done
if [ "$FAILED" -ne 0 ]; then
SUCCESS=0
fi
}
# Configure the test environment; run them early so that we're certain
......@@ -160,7 +183,9 @@ if [ -z "$SKIP_ONLINE_TESTS" ]; then
echo "## Running (online) tests"
echo "##############################################################################"
export GITTEST_FLAKY_RETRY=5
run_test online
unset GITTEST_FLAKY_RETRY
fi
if [ -z "$SKIP_GITDAEMON_TESTS" ]; then
......@@ -220,7 +245,7 @@ fi
cleanup
if [ "$SUCCESS" -ne "1" ]; then
if [ "$SUCCESS" -ne 1 ]; then
echo "Some tests failed."
exit 1
fi
......
v0.28.5
-------
This is a bugfix release with the following changes:
- Fix an out-of-bounds read when applying patches that do not end
with a newline.
- Fix an out-of-bounds read when decoding specially crafted
binary patches.
- Fix an out-of-bounds read when receiving a specially crafted
"OK" packet via the smarthttp transport.
- Fix lifetime for parsed patches depending on the lifetime of
the parsed buffe.
- Several fixes when parsing and applying patches.
- Fix computed patch IDs for patches that have no newline at end
of file.
- Fix applying patches to trees that add new files.
- Do not read configuration from a user's home directory if
running in a sandboxed environment.
- Fix handling of nested ignore rules overriding wildcard
unignores in parent directories.
- Fix reference locks not being correctly honored on Unix
systems.
- Follow 308 redirects when fetching or pushing from remote
repositories on Windows.
- Fix a race when detaching the libgit2 library on Windows.
- Update the "binary" gitattribute macro to match git's change
to "-diff -merge -text -crlf".
- Refuse to delete the HEAD reference.
- Fixes for several memory leaks.
v0.28.4
--------
......
......@@ -7,10 +7,10 @@
#ifndef INCLUDE_git_version_h__
#define INCLUDE_git_version_h__
#define LIBGIT2_VERSION "0.28.4"
#define LIBGIT2_VERSION "0.28.5"
#define LIBGIT2_VER_MAJOR 0
#define LIBGIT2_VER_MINOR 28
#define LIBGIT2_VER_REVISION 4
#define LIBGIT2_VER_REVISION 5
#define LIBGIT2_VER_PATCH 0
#define LIBGIT2_SOVERSION 28
......
#!/usr/bin/env python
from collections import namedtuple
import argparse
import base64
import copy
import json
import subprocess
import sys
import urllib.parse
import urllib.request
import urllib.error
class Error(Exception):
pass
class Version(object):
def __init__(self, version):
versions = version.split(sep='.')
if len(versions) < 2 or len(versions) > 3:
raise Error("Invalid version string '{}'".format(version))
self.major = int(versions[0])
self.minor = int(versions[1])
self.revision = int(versions[2]) if len(versions) == 3 else 0
def __str__(self):
return '{}.{}.{}'.format(self.major, self.minor, self.revision)
def __eq__(self, other):
return self.major == other.major and self.minor == other.minor and self.revision == other.revision
def verify_version(version):
expected = {
'VERSION': [ '"{}"'.format(version), None ],
'VER_MAJOR': [ str(version.major), None ],
'VER_MINOR': [ str(version.minor), None ],
'VER_REVISION': [ str(version.revision), None ],
'VER_PATCH': [ '0', None ],
'SOVERSION': [ '{}'.format(version.minor), None ],
}
with open('include/git2/version.h') as f:
lines = f.readlines()
for key in expected.keys():
define = '#define LIBGIT2_{} '.format(key)
for line in lines:
if line.startswith(define):
expected[key][1] = line[len(define):].strip()
break
else:
raise Error("version.h: missing define for '{}'".format(key))
for k, v in expected.items():
if v[0] != v[1]:
raise Error("version.h: define '{}' does not match (got '{}', expected '{}')".format(k, v[0], v[1]))
def generate_relnotes(tree, version):
with open('docs/changelog.md') as f:
lines = f.readlines()
if not lines[0].startswith('v'):
raise Error("changelog.md: missing section for v{}".format(version))
try:
v = Version(lines[0][1:].strip())
except:
raise Error("changelog.md: invalid version string {}".format(lines[0].strip()))
if v != version:
raise Error("changelog.md: changelog version doesn't match (got {}, expected {})".format(v, version))
if not lines[1].startswith('----'):
raise Error("changelog.md: missing version header")
if lines[2] != '\n':
raise Error("changelog.md: missing newline after version header")
for i, line in enumerate(lines[3:]):
if not line.startswith('v'):
continue
try:
Version(line[1:].strip())
break
except:
continue
else:
raise Error("changelog.md: cannot find section header of preceding release")
return ''.join(lines[3:i + 3]).strip()
def git(*args):
process = subprocess.run([ 'git', *args ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if process.returncode != 0:
raise Error('Failed executing git {}: {}'.format(' '.join(args), process.stderr.decode()))
return process.stdout
def post(url, data, contenttype, user, password):
request = urllib.request.Request(url, data=data)
request.add_header('Accept', 'application/json')
request.add_header('Content-Type', contenttype)
request.add_header('Content-Length', len(data))
request.add_header('Authorization', 'Basic ' + base64.b64encode('{}:{}'.format(user, password).encode()).decode())
try:
response = urllib.request.urlopen(request)
if response.getcode() != 201:
raise Error("POST to '{}' failed: {}".format(url, response.reason))
except urllib.error.URLError as e:
raise Error("POST to '{}' failed: {}".format(url, e))
data = json.load(response)
return data
def generate_asset(version, tree, archive_format):
Asset = namedtuple('Asset', ['name', 'label', 'mimetype', 'data'])
mimetype = 'application/{}'.format('gzip' if archive_format == 'tar.gz' else 'zip')
return Asset(
"libgit2-{}.{}".format(version, archive_format), "Release sources: libgit2-{}.{}".format(version, archive_format), mimetype,
git('archive', '--format', archive_format, '--prefix', 'libgit2-{}/'.format(version), tree)
)
def release(args):
params = {
"tag_name": 'v' + str(args.version),
"name": 'libgit2 v' + str(args.version),
"target_commitish": git('rev-parse', args.tree).decode().strip(),
"body": generate_relnotes(args.tree, args.version),
}
assets = [
generate_asset(args.version, args.tree, 'tar.gz'),
generate_asset(args.version, args.tree, 'zip'),
]
if args.dryrun:
for k, v in params.items():
print('{}: {}'.format(k, v))
for asset in assets:
print('asset: name={}, label={}, mimetype={}, bytes={}'.format(asset.name, asset.label, asset.mimetype, len(asset.data)))
return
try:
url = 'https://api.github.com/repos/{}/releases'.format(args.repository)
response = post(url, json.dumps(params).encode(), 'application/json', args.user, args.password)
except Error as e:
raise Error('Could not create release: ' + str(e))
for asset in assets:
try:
url = list(urllib.parse.urlparse(response['upload_url'].split('{?')[0]))
url[4] = urllib.parse.urlencode({ 'name': asset.name, 'label': asset.label })
post(urllib.parse.urlunparse(url), asset.data, asset.mimetype, args.user, args.password)
except Error as e:
raise Error('Could not upload asset: ' + str(e))
def main():
parser = argparse.ArgumentParser(description='Create a libgit2 release')
parser.add_argument('--tree', default='HEAD', help='tree to create release for (default: HEAD)')
parser.add_argument('--dryrun', action='store_true', help='generate release, but do not post it')
parser.add_argument('--repository', default='libgit2/libgit2', help='GitHub repository to create repository in')
parser.add_argument('--user', help='user to authenitcate as')
parser.add_argument('--password', help='password to authenticate with')
parser.add_argument('version', type=Version, help='version of the new release')
args = parser.parse_args()
verify_version(args.version)
release(args)
if __name__ == '__main__':
try:
main()
except Error as e:
print(e)
sys.exit(1)
......@@ -59,7 +59,7 @@ static int patch_image_init_fromstr(
git_pool_init(&out->pool, sizeof(git_diff_line));
for (start = in; start < in + in_len; start = end) {
end = memchr(start, '\n', in_len);
end = memchr(start, '\n', in_len - (start - in));
if (end == NULL)
end = in + in_len;
......@@ -199,23 +199,34 @@ static int apply_hunk(
for (i = 0; i < hunk->line_count; i++) {
size_t linenum = hunk->line_start + i;
git_diff_line *line = git_array_get(patch->lines, linenum);
git_diff_line *line = git_array_get(patch->lines, linenum), *prev;
if (!line) {
error = apply_err("preimage does not contain line %"PRIuZ, linenum);
goto done;
}
if (line->origin == GIT_DIFF_LINE_CONTEXT ||
line->origin == GIT_DIFF_LINE_DELETION) {
if ((error = git_vector_insert(&preimage.lines, line)) < 0)
goto done;
}
if (line->origin == GIT_DIFF_LINE_CONTEXT ||
line->origin == GIT_DIFF_LINE_ADDITION) {
if ((error = git_vector_insert(&postimage.lines, line)) < 0)
goto done;
switch (line->origin) {
case GIT_DIFF_LINE_CONTEXT_EOFNL:
case GIT_DIFF_LINE_DEL_EOFNL:
case GIT_DIFF_LINE_ADD_EOFNL:
prev = i ? git_array_get(patch->lines, linenum - 1) : NULL;
if (prev && prev->content[prev->content_len - 1] == '\n')
prev->content_len -= 1;
break;
case GIT_DIFF_LINE_CONTEXT:
if ((error = git_vector_insert(&preimage.lines, line)) < 0 ||
(error = git_vector_insert(&postimage.lines, line)) < 0)
goto done;
break;
case GIT_DIFF_LINE_DELETION:
if ((error = git_vector_insert(&preimage.lines, line)) < 0)
goto done;
break;
case GIT_DIFF_LINE_ADDITION:
if ((error = git_vector_insert(&postimage.lines, line)) < 0)
goto done;
break;
}
}
......@@ -631,9 +642,12 @@ int git_apply_to_tree(
for (i = 0; i < git_diff_num_deltas(diff); i++) {
delta = git_diff_get_delta(diff, i);
if ((error = git_index_remove(postimage,
delta->old_file.path, 0)) < 0)
goto done;
if (delta->status == GIT_DELTA_DELETED ||
delta->status == GIT_DELTA_RENAMED) {
if ((error = git_index_remove(postimage,
delta->old_file.path, 0)) < 0)
goto done;
}
}
if ((error = apply_deltas(repo, pre_reader, NULL, post_reader, postimage, diff, &opts)) < 0)
......
......@@ -413,7 +413,7 @@ int git_attr_cache__init(git_repository *repo)
git_config_free(cfg);
/* insert default macros */
return git_attr_add_macro(repo, "binary", "-diff -crlf -text");
return git_attr_add_macro(repo, "binary", "-diff -merge -text -crlf");
cancel:
attr_cache__free(cache);
......
......@@ -408,7 +408,7 @@ on_error:
static bool hunk_is_bufferblame(git_blame_hunk *hunk)
{
return git_oid_iszero(&hunk->final_commit_id);
return hunk && git_oid_iszero(&hunk->final_commit_id);
}
static int buffer_hunk_cb(
......
......@@ -18,7 +18,8 @@ char git_buf__initbuf[1];
char git_buf__oom[1];
#define ENSURE_SIZE(b, d) \
if ((d) > (b)->asize && git_buf_grow((b), (d)) < 0)\
if ((b)->ptr == git_buf__oom || \
((d) > (b)->asize && git_buf_grow((b), (d)) < 0))\
return -1;
......@@ -58,20 +59,26 @@ int git_buf_try_grow(
new_ptr = NULL;
} else {
new_size = buf->asize;
/*
* Grow the allocated buffer by 1.5 to allow
* re-use of memory holes resulting from the
* realloc. If this is still too small, then just
* use the target size.
*/
if ((new_size = (new_size << 1) - (new_size >> 1)) < target_size)
new_size = target_size;
new_ptr = buf->ptr;
}
/* grow the buffer size by 1.5, until it's big enough
* to fit our target size */
while (new_size < target_size)
new_size = (new_size << 1) - (new_size >> 1);
/* round allocation up to multiple of 8 */
new_size = (new_size + 7) & ~7;
if (new_size < buf->size) {
if (mark_oom)
if (mark_oom) {
if (buf->ptr && buf->ptr != git_buf__initbuf)
git__free(buf->ptr);
buf->ptr = git_buf__oom;
}
git_error_set_oom();
return -1;
......
......@@ -30,7 +30,7 @@ static int write_cherrypick_head(
int error = 0;
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_CHERRYPICK_HEAD_FILE)) >= 0 &&
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) >= 0 &&
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_CHERRYPICK_FILE_MODE)) >= 0 &&
(error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0)
error = git_filebuf_commit(&file);
......@@ -51,7 +51,7 @@ static int write_merge_msg(
int error = 0;
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 ||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) < 0 ||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_CHERRYPICK_FILE_MODE)) < 0 ||
(error = git_filebuf_printf(&file, "%s", commit_msg)) < 0)
goto cleanup;
......
......@@ -443,7 +443,7 @@ out:
return error;
}
static int line_cb(
static int patchid_line_cb(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
const git_diff_line *line,
......@@ -465,6 +465,14 @@ static int line_cb(
break;
case GIT_DIFF_LINE_CONTEXT:
break;
case GIT_DIFF_LINE_CONTEXT_EOFNL:
case GIT_DIFF_LINE_ADD_EOFNL:
case GIT_DIFF_LINE_DEL_EOFNL:
/*
* Ignore EOF without newlines for patch IDs as whitespace is
* not supposed to be significant.
*/
return 0;
default:
git_error_set(GIT_ERROR_PATCH, "invalid line origin for patch");
return -1;
......@@ -501,7 +509,7 @@ int git_diff_patchid(git_oid *out, git_diff *diff, git_diff_patchid_options *opt
if ((error = git_hash_ctx_init(&args.ctx)) < 0)
goto out;
if ((error = git_diff_foreach(diff, file_cb, NULL, NULL, line_cb, &args)) < 0)
if ((error = git_diff_foreach(diff, file_cb, NULL, NULL, patchid_line_cb, &args)) < 0)
goto out;
if ((error = (flush_hunk(&args.result, &args.ctx))) < 0)
......
......@@ -1262,29 +1262,31 @@ cleanup:
return error;
}
#define DIFF_FROM_ITERATORS(MAKE_FIRST, FLAGS_FIRST, MAKE_SECOND, FLAGS_SECOND) do { \
git_iterator *a = NULL, *b = NULL; \
char *pfx = (opts && !(opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) ? \
git_pathspec_prefix(&opts->pathspec) : NULL; \
git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, \
b_opts = GIT_ITERATOR_OPTIONS_INIT; \
a_opts.flags = FLAGS_FIRST; \
a_opts.start = pfx; \
a_opts.end = pfx; \
b_opts.flags = FLAGS_SECOND; \
b_opts.start = pfx; \
b_opts.end = pfx; \
GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \
if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { \
a_opts.pathlist.strings = opts->pathspec.strings; \
a_opts.pathlist.count = opts->pathspec.count; \
b_opts.pathlist.strings = opts->pathspec.strings; \
b_opts.pathlist.count = opts->pathspec.count; \
} \
if (!error && !(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
error = git_diff__from_iterators(&diff, repo, a, b, opts); \
git__free(pfx); git_iterator_free(a); git_iterator_free(b); \
} while (0)
static int diff_prepare_iterator_opts(char **prefix, git_iterator_options *a, int aflags,
git_iterator_options *b, int bflags,
const git_diff_options *opts)
{
GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
*prefix = NULL;
if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) {
a->pathlist.strings = opts->pathspec.strings;
a->pathlist.count = opts->pathspec.count;
b->pathlist.strings = opts->pathspec.strings;
b->pathlist.count = opts->pathspec.count;
} else if (opts) {
*prefix = git_pathspec_prefix(&opts->pathspec);
GIT_ERROR_CHECK_ALLOC(prefix);
}
a->flags = aflags;
b->flags = bflags;
a->start = b->start = *prefix;
a->end = b->end = *prefix;
return 0;
}
int git_diff_tree_to_tree(
git_diff **out,
......@@ -1293,8 +1295,12 @@ int git_diff_tree_to_tree(
git_tree *new_tree,
const git_diff_options *opts)
{
git_diff *diff = NULL;
git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE;
git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT,
b_opts = GIT_ITERATOR_OPTIONS_INIT;
git_iterator *a = NULL, *b = NULL;
git_diff *diff = NULL;
char *prefix = NULL;
int error = 0;
assert(out && repo);
......@@ -1308,13 +1314,19 @@ int git_diff_tree_to_tree(
if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0)
iflag = GIT_ITERATOR_IGNORE_CASE;
DIFF_FROM_ITERATORS(
git_iterator_for_tree(&a, old_tree, &a_opts), iflag,
git_iterator_for_tree(&b, new_tree, &b_opts), iflag
);
if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, iflag, &b_opts, iflag, opts)) < 0 ||
(error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 ||
(error = git_iterator_for_tree(&b, new_tree, &b_opts)) < 0 ||
(error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0)
goto out;
if (!error)
*out = diff;
*out = diff;
diff = NULL;
out:
git_iterator_free(a);
git_iterator_free(b);
git_diff_free(diff);
git__free(prefix);
return error;
}
......@@ -1337,9 +1349,13 @@ int git_diff_tree_to_index(
git_index *index,
const git_diff_options *opts)
{
git_diff *diff = NULL;
git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE |
GIT_ITERATOR_INCLUDE_CONFLICTS;
git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT,
b_opts = GIT_ITERATOR_OPTIONS_INIT;
git_iterator *a = NULL, *b = NULL;
git_diff *diff = NULL;
char *prefix = NULL;
bool index_ignore_case = false;
int error = 0;
......@@ -1352,17 +1368,23 @@ int git_diff_tree_to_index(
index_ignore_case = index->ignore_case;
DIFF_FROM_ITERATORS(
git_iterator_for_tree(&a, old_tree, &a_opts), iflag,
git_iterator_for_index(&b, repo, index, &b_opts), iflag
);
if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, iflag, &b_opts, iflag, opts)) < 0 ||
(error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 ||
(error = git_iterator_for_index(&b, repo, index, &b_opts)) < 0 ||
(error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0)
goto out;
/* if index is in case-insensitive order, re-sort deltas to match */
if (!error && index_ignore_case)
if (index_ignore_case)
git_diff__set_ignore_case(diff, true);
if (!error)
*out = diff;
*out = diff;
diff = NULL;
out:
git_iterator_free(a);
git_iterator_free(b);
git_diff_free(diff);
git__free(prefix);
return error;
}
......@@ -1373,7 +1395,11 @@ int git_diff_index_to_workdir(
git_index *index,
const git_diff_options *opts)
{
git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT,
b_opts = GIT_ITERATOR_OPTIONS_INIT;
git_iterator *a = NULL, *b = NULL;
git_diff *diff = NULL;
char *prefix = NULL;
int error = 0;
assert(out && repo);
......@@ -1383,20 +1409,24 @@ int git_diff_index_to_workdir(
if (!index && (error = diff_load_index(&index, repo)) < 0)
return error;
DIFF_FROM_ITERATORS(
git_iterator_for_index(&a, repo, index, &a_opts),
GIT_ITERATOR_INCLUDE_CONFLICTS,
git_iterator_for_workdir(&b, repo, index, NULL, &b_opts),
GIT_ITERATOR_DONT_AUTOEXPAND
);
if (!error && (diff->opts.flags & GIT_DIFF_UPDATE_INDEX) != 0 &&
((git_diff_generated *)diff)->index_updated)
error = git_index_write(index);
if (!error)
*out = diff;
if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, GIT_ITERATOR_INCLUDE_CONFLICTS,
&b_opts, GIT_ITERATOR_DONT_AUTOEXPAND, opts)) < 0 ||
(error = git_iterator_for_index(&a, repo, index, &a_opts)) < 0 ||
(error = git_iterator_for_workdir(&b, repo, index, NULL, &b_opts)) < 0 ||
(error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0)
goto out;
if ((diff->opts.flags & GIT_DIFF_UPDATE_INDEX) && ((git_diff_generated *)diff)->index_updated)
if ((error = git_index_write(index)) < 0)
goto out;
*out = diff;
diff = NULL;
out:
git_iterator_free(a);
git_iterator_free(b);
git_diff_free(diff);
git__free(prefix);
return error;
}
......@@ -1407,24 +1437,33 @@ int git_diff_tree_to_workdir(
git_tree *old_tree,
const git_diff_options *opts)
{
git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT,
b_opts = GIT_ITERATOR_OPTIONS_INIT;
git_iterator *a = NULL, *b = NULL;
git_diff *diff = NULL;
char *prefix = NULL;
git_index *index;
int error = 0;
int error;
assert(out && repo);
*out = NULL;
if ((error = git_repository_index__weakptr(&index, repo)))
return error;
DIFF_FROM_ITERATORS(
git_iterator_for_tree(&a, old_tree, &a_opts), 0,
git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts), GIT_ITERATOR_DONT_AUTOEXPAND
);
if (!error)
*out = diff;
if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, 0,
&b_opts, GIT_ITERATOR_DONT_AUTOEXPAND, opts) < 0) ||
(error = git_repository_index__weakptr(&index, repo)) < 0 ||
(error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 ||
(error = git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts)) < 0 ||
(error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0)
goto out;
*out = diff;
diff = NULL;
out:
git_iterator_free(a);
git_iterator_free(b);
git_diff_free(diff);
git__free(prefix);
return error;
}
......@@ -1468,24 +1507,35 @@ int git_diff_index_to_index(
git_index *new_index,
const git_diff_options *opts)
{
git_diff *diff;
int error = 0;
git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT,
b_opts = GIT_ITERATOR_OPTIONS_INIT;
git_iterator *a = NULL, *b = NULL;
git_diff *diff = NULL;
char *prefix = NULL;
int error;
assert(out && old_index && new_index);
*out = NULL;
DIFF_FROM_ITERATORS(
git_iterator_for_index(&a, repo, old_index, &a_opts), GIT_ITERATOR_DONT_IGNORE_CASE,
git_iterator_for_index(&b, repo, new_index, &b_opts), GIT_ITERATOR_DONT_IGNORE_CASE
);
if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, GIT_ITERATOR_DONT_IGNORE_CASE,
&b_opts, GIT_ITERATOR_DONT_IGNORE_CASE, opts) < 0) ||
(error = git_iterator_for_index(&a, repo, old_index, &a_opts)) < 0 ||
(error = git_iterator_for_index(&b, repo, new_index, &b_opts)) < 0 ||
(error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0)
goto out;
/* if index is in case-insensitive order, re-sort deltas to match */
if (!error && (old_index->ignore_case || new_index->ignore_case))
if (old_index->ignore_case || new_index->ignore_case)
git_diff__set_ignore_case(diff, true);
if (!error)
*out = diff;
*out = diff;
diff = NULL;
out:
git_iterator_free(a);
git_iterator_free(b);
git_diff_free(diff);
git__free(prefix);
return error;
}
......
......@@ -44,18 +44,14 @@ static int verify_last_error(git_filebuf *file)
static int lock_file(git_filebuf *file, int flags, mode_t mode)
{
if (git_path_exists(file->path_lock) == true) {
if (flags & GIT_FILEBUF_FORCE)
p_unlink(file->path_lock);
else {
git_error_clear(); /* actual OS error code just confuses */
git_error_set(GIT_ERROR_OS,
"failed to lock file '%s' for writing", file->path_lock);
return GIT_ELOCKED;
}
git_error_clear(); /* actual OS error code just confuses */
git_error_set(GIT_ERROR_OS,
"failed to lock file '%s' for writing", file->path_lock);
return GIT_ELOCKED;
}
/* create path to the file buffer is required */
if (flags & GIT_FILEBUF_FORCE) {
if (flags & GIT_FILEBUF_CREATE_LEADING_DIRS) {
/* XXX: Should dirmode here be configurable? Or is 0777 always fine? */
file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, mode);
} else {
......
......@@ -19,7 +19,7 @@
#define GIT_FILEBUF_HASH_CONTENTS (1 << 0)
#define GIT_FILEBUF_APPEND (1 << 2)
#define GIT_FILEBUF_FORCE (1 << 3)
#define GIT_FILEBUF_CREATE_LEADING_DIRS (1 << 3)
#define GIT_FILEBUF_TEMPORARY (1 << 4)
#define GIT_FILEBUF_DO_NOT_BUFFER (1 << 5)
#define GIT_FILEBUF_FSYNC (1 << 6)
......
......@@ -476,6 +476,7 @@ int git_futils_mkdir(
break;
} else if (errno != ENOENT) {
git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr);
error = -1;
goto done;
}
......
......@@ -141,14 +141,21 @@ static void shutdown_common(void)
*/
#if defined(GIT_THREADS) && defined(GIT_WIN32)
static DWORD _tls_index;
static DWORD _fls_index;
static volatile LONG _mutex = 0;
static void WINAPI fls_free(void *st)
{
git__global_state_cleanup(st);
git__free(st);
}
static int synchronized_threads_init(void)
{
int error;
_tls_index = TlsAlloc();
if ((_fls_index = FlsAlloc(fls_free)) == FLS_OUT_OF_INDEXES)
return -1;
git_threads_init();
......@@ -190,9 +197,7 @@ int git_libgit2_shutdown(void)
if ((ret = git_atomic_dec(&git__n_inits)) == 0) {
shutdown_common();
git__free_tls_data();
TlsFree(_tls_index);
FlsFree(_fls_index);
git_mutex_free(&git__mwindow_mutex);
#if defined(GIT_MSVC_CRTDBG)
......@@ -213,7 +218,7 @@ git_global_st *git__global_state(void)
assert(git_atomic_get(&git__n_inits) > 0);
if ((ptr = TlsGetValue(_tls_index)) != NULL)
if ((ptr = FlsGetValue(_fls_index)) != NULL)
return ptr;
ptr = git__calloc(1, sizeof(git_global_st));
......@@ -222,43 +227,10 @@ git_global_st *git__global_state(void)
git_buf_init(&ptr->error_buf, 0);
TlsSetValue(_tls_index, ptr);
FlsSetValue(_fls_index, ptr);
return ptr;
}
/**
* Free the TLS data associated with this thread.
* This should only be used by the thread as it
* is exiting.
*/
void git__free_tls_data(void)
{
void *ptr = TlsGetValue(_tls_index);
if (!ptr)
return;
git__global_state_cleanup(ptr);
git__free(ptr);
TlsSetValue(_tls_index, NULL);
}
BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpvReserved)
{
GIT_UNUSED(hInstDll);
GIT_UNUSED(lpvReserved);
/* This is how Windows lets us know our thread is being shut down */
if (fdwReason == DLL_THREAD_DETACH) {
git__free_tls_data();
}
/*
* Windows pays attention to this during library loading. We don't do anything
* so we trivially succeed.
*/
return TRUE;
}
#elif defined(GIT_THREADS) && defined(_POSIX_THREADS)
static pthread_key_t _tls_key;
......
......@@ -35,8 +35,6 @@ typedef void (*git_global_shutdown_fn)(void);
extern void git__on_shutdown(git_global_shutdown_fn callback);
extern void git__free_tls_data(void);
extern const char *git_libgit2__user_agent(void);
extern const char *git_libgit2__ssl_ciphers(void);
......
......@@ -453,7 +453,7 @@ static bool ignore_lookup_in_rules(
int git_ignore__lookup(
int *out, git_ignores *ignores, const char *pathname, git_dir_flag dir_flag)
{
unsigned int i;
size_t i;
git_attr_file *file;
git_attr_path path;
......@@ -467,8 +467,11 @@ int git_ignore__lookup(
if (ignore_lookup_in_rules(out, ignores->ign_internal, &path))
goto cleanup;
/* next process files in the path */
git_vector_foreach(&ignores->ign_path, i, file) {
/* next process files in the path.
* this process has to process ignores in reverse order
* to ensure correct prioritization of rules
*/
git_vector_rforeach(&ignores->ign_path, i, file) {
if (ignore_lookup_in_rules(out, file, &path))
goto cleanup;
}
......
......@@ -65,15 +65,25 @@ GIT_INLINE(int) git__is_int(long long p)
# error compiler has add with overflow intrinsics but SIZE_MAX is unknown
# endif
# define git__add_int_overflow(out, one, two) \
__builtin_sadd_overflow(one, two, out)
# define git__sub_int_overflow(out, one, two) \
__builtin_ssub_overflow(one, two, out)
/* Use Microsoft's safe integer handling functions where available */
#elif defined(_MSC_VER)
# define ENABLE_INTSAFE_SIGNED_FUNCTIONS
# include <intsafe.h>
# define git__add_sizet_overflow(out, one, two) \
(SizeTAdd(one, two, out) != S_OK)
# define git__multiply_sizet_overflow(out, one, two) \
(SizeTMult(one, two, out) != S_OK)
#define git__add_int_overflow(out, one, two) \
(IntAdd(one, two, out) != S_OK)
#define git__sub_int_overflow(out, one, two) \
(IntSub(one, two, out) != S_OK)
#else
......@@ -101,6 +111,24 @@ GIT_INLINE(bool) git__multiply_sizet_overflow(size_t *out, size_t one, size_t tw
return false;
}
GIT_INLINE(bool) git__add_int_overflow(int *out, int one, int two)
{
if ((two > 0 && one > (INT_MAX - two)) ||
(two < 0 && one < (INT_MIN - two)))
return true;
*out = one + two;
return false;
}
GIT_INLINE(bool) git__sub_int_overflow(int *out, int one, int two)
{
if ((two > 0 && one < (INT_MIN + two)) ||
(two < 0 && one > (INT_MAX + two)))
return true;
*out = one - two;
return false;
}
#endif
#endif
......@@ -543,8 +543,6 @@ static int tree_iterator_frame_init(
new_frame = git_array_alloc(iter->frames);
GIT_ERROR_CHECK_ALLOC(new_frame);
memset(new_frame, 0, sizeof(tree_iterator_frame));
if ((error = git_tree_dup(&dup, tree)) < 0)
goto done;
......@@ -552,19 +550,22 @@ static int tree_iterator_frame_init(
new_frame->tree = dup;
if (frame_entry &&
(error = tree_iterator_compute_path(&new_frame->path, frame_entry)) < 0)
(error = tree_iterator_compute_path(&new_frame->path, frame_entry)) < 0)
goto done;
cmp = iterator__ignore_case(&iter->base) ?
tree_iterator_entry_sort_icase : NULL;
if ((error = git_vector_init(
&new_frame->entries, dup->entries.size, cmp)) < 0)
if ((error = git_vector_init(&new_frame->entries,
dup->entries.size, cmp)) < 0)
goto done;
git_array_foreach(dup->entries, i, tree_entry) {
new_entry = git_pool_malloc(&iter->entry_pool, 1);
GIT_ERROR_CHECK_ALLOC(new_entry);
if ((new_entry = git_pool_malloc(&iter->entry_pool, 1)) == NULL) {
git_error_set_oom();
error = -1;
goto done;
}
new_entry->tree_entry = tree_entry;
new_entry->parent_path = new_frame->path.ptr;
......
......@@ -2430,7 +2430,7 @@ static int write_merge_head(
assert(repo && heads);
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_HEAD_FILE)) < 0 ||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0)
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) < 0)
goto cleanup;
for (i = 0; i < heads_len; i++) {
......@@ -2458,7 +2458,7 @@ static int write_merge_mode(git_repository *repo)
assert(repo);
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MODE_FILE)) < 0 ||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0)
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) < 0)
goto cleanup;
if ((error = git_filebuf_write(&file, "no-ff", 5)) < 0)
......@@ -2689,7 +2689,7 @@ static int write_merge_msg(
entries[i].merge_head = heads[i];
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 ||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0 ||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) < 0 ||
(error = git_filebuf_write(&file, "Merge ", 6)) < 0)
goto cleanup;
......
......@@ -836,7 +836,7 @@ static int patch_generated_line_cb(
{
git_patch_generated *patch = payload;
git_patch_hunk *hunk;
git_diff_line *line;
git_diff_line *line;
GIT_UNUSED(delta);
GIT_UNUSED(hunk_);
......
......@@ -58,31 +58,36 @@ static int parse_header_path_buf(git_buf *path, git_patch_parse_ctx *ctx, size_t
int error;
if ((error = git_buf_put(path, ctx->parse_ctx.line, path_len)) < 0)
goto done;
return error;
git_parse_advance_chars(&ctx->parse_ctx, path_len);
git_buf_rtrim(path);
if (path->size > 0 && path->ptr[0] == '"')
error = git_buf_unquote(path);
if (error < 0)
goto done;
if (path->size > 0 && path->ptr[0] == '"' &&
(error = git_buf_unquote(path)) < 0)
return error;
git_path_squash_slashes(path);
done:
return error;
if (!path->size)
return git_parse_err("patch contains empty path at line %"PRIuZ,
ctx->parse_ctx.line_num);
return 0;
}
static int parse_header_path(char **out, git_patch_parse_ctx *ctx)
{
git_buf path = GIT_BUF_INIT;
int error = parse_header_path_buf(&path, ctx, header_path_len(ctx));
int error;
if ((error = parse_header_path_buf(&path, ctx, header_path_len(ctx))) < 0)
goto out;
*out = git_buf_detach(&path);
out:
git_buf_dispose(&path);
return error;
}
......@@ -92,6 +97,12 @@ static int parse_header_git_oldpath(
git_buf old_path = GIT_BUF_INIT;
int error;
if (patch->old_path) {
error = git_parse_err("patch contains duplicate old path at line %"PRIuZ,
ctx->parse_ctx.line_num);
goto out;
}
if ((error = parse_header_path_buf(&old_path, ctx, ctx->parse_ctx.line_len - 1)) < 0)
goto out;
......@@ -108,9 +119,14 @@ static int parse_header_git_newpath(
git_buf new_path = GIT_BUF_INIT;
int error;
if ((error = parse_header_path_buf(&new_path, ctx, ctx->parse_ctx.line_len - 1)) < 0)
if (patch->new_path) {
error = git_parse_err("patch contains duplicate new path at line %"PRIuZ,
ctx->parse_ctx.line_num);
goto out;
}
if ((error = parse_header_path_buf(&new_path, ctx, ctx->parse_ctx.line_len - 1)) < 0)
goto out;
patch->new_path = git_buf_detach(&new_path);
out:
......@@ -377,6 +393,7 @@ static const parse_header_transition transitions[] = {
{ "index " , STATE_DIFF, STATE_INDEX, parse_header_git_index },
{ "index " , STATE_END, STATE_INDEX, parse_header_git_index },
{ "--- " , STATE_DIFF, STATE_PATH, parse_header_git_oldpath },
{ "--- " , STATE_INDEX, STATE_PATH, parse_header_git_oldpath },
{ "+++ " , STATE_PATH, STATE_END, parse_header_git_newpath },
{ "GIT binary patch" , STATE_INDEX, STATE_END, NULL },
......@@ -394,6 +411,7 @@ static const parse_header_transition transitions[] = {
/* Next patch */
{ "diff --git " , STATE_END, 0, NULL },
{ "@@ -" , STATE_END, 0, NULL },
{ "-- " , STATE_INDEX, 0, NULL },
{ "-- " , STATE_END, 0, NULL },
};
......@@ -524,6 +542,14 @@ fail:
return -1;
}
static int eof_for_origin(int origin) {
if (origin == GIT_DIFF_LINE_ADDITION)
return GIT_DIFF_LINE_ADD_EOFNL;
if (origin == GIT_DIFF_LINE_DELETION)
return GIT_DIFF_LINE_DEL_EOFNL;
return GIT_DIFF_LINE_CONTEXT_EOFNL;
}
static int parse_hunk_body(
git_patch_parsed *patch,
git_patch_hunk *hunk,
......@@ -534,6 +560,7 @@ static int parse_hunk_body(
int oldlines = hunk->hunk.old_lines;
int newlines = hunk->hunk.new_lines;
int last_origin = 0;
for (;
ctx->parse_ctx.remain_len > 1 &&
......@@ -541,11 +568,17 @@ static int parse_hunk_body(
!git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -");
git_parse_advance_line(&ctx->parse_ctx)) {
int old_lineno, new_lineno, origin, prefix = 1;
char c;
int origin;
int prefix = 1;
int old_lineno = hunk->hunk.old_start + (hunk->hunk.old_lines - oldlines);
int new_lineno = hunk->hunk.new_start + (hunk->hunk.new_lines - newlines);
if (git__add_int_overflow(&old_lineno, hunk->hunk.old_start, hunk->hunk.old_lines) ||
git__sub_int_overflow(&old_lineno, old_lineno, oldlines) ||
git__add_int_overflow(&new_lineno, hunk->hunk.new_start, hunk->hunk.new_lines) ||
git__sub_int_overflow(&new_lineno, new_lineno, newlines)) {
error = git_parse_err("unrepresentable line count at line %"PRIuZ,
ctx->parse_ctx.line_num);
goto done;
}
if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') {
error = git_parse_err("invalid patch instruction at line %"PRIuZ,
......@@ -578,6 +611,21 @@ static int parse_hunk_body(
old_lineno = -1;
break;
case '\\':
/*
* If there are no oldlines left, then this is probably
* the "\ No newline at end of file" marker. Do not
* verify its format, as it may be localized.
*/
if (!oldlines) {
prefix = 0;
origin = eof_for_origin(last_origin);
old_lineno = -1;
new_lineno = -1;
break;
}
/* fall through */
default:
error = git_parse_err("invalid patch hunk at line %"PRIuZ, ctx->parse_ctx.line_num);
goto done;
......@@ -588,8 +636,9 @@ static int parse_hunk_body(
memset(line, 0x0, sizeof(git_diff_line));
line->content = ctx->parse_ctx.line + prefix;
line->content_len = ctx->parse_ctx.line_len - prefix;
line->content = git__strndup(ctx->parse_ctx.line + prefix, line->content_len);
GIT_ERROR_CHECK_ALLOC(line->content);
line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len;
line->origin = origin;
line->num_lines = 1;
......@@ -597,6 +646,8 @@ static int parse_hunk_body(
line->new_lineno = new_lineno;
hunk->line_count++;
last_origin = origin;
}
if (oldlines || newlines) {
......@@ -606,7 +657,8 @@ static int parse_hunk_body(
goto done;
}
/* Handle "\ No newline at end of file". Only expect the leading
/*
* Handle "\ No newline at end of file". Only expect the leading
* backslash, though, because the rest of the string could be
* localized. Because `diff` optimizes for the case where you
* want to apply the patch by hand.
......@@ -617,11 +669,25 @@ static int parse_hunk_body(
line = git_array_get(patch->base.lines, git_array_size(patch->base.lines) - 1);
if (line->content_len < 1) {
error = git_parse_err("cannot trim trailing newline of empty line");
error = git_parse_err("last line has no trailing newline");
goto done;
}
line->content_len--;
line = git_array_alloc(patch->base.lines);
GIT_ERROR_CHECK_ALLOC(line);
memset(line, 0x0, sizeof(git_diff_line));
line->content_len = ctx->parse_ctx.line_len;
line->content = git__strndup(ctx->parse_ctx.line, line->content_len);
GIT_ERROR_CHECK_ALLOC(line->content);
line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len;
line->origin = eof_for_origin(last_origin);
line->num_lines = 1;
line->old_lineno = -1;
line->new_lineno = -1;
hunk->line_count++;
git_parse_advance_line(&ctx->parse_ctx);
}
......@@ -730,7 +796,7 @@ static int parse_patch_binary_side(
encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5;
if (encoded_len > ctx->parse_ctx.line_len - 1) {
if (!encoded_len || !ctx->parse_ctx.line_len || encoded_len > ctx->parse_ctx.line_len - 1) {
error = git_parse_err("truncated binary data at line %"PRIuZ, ctx->parse_ctx.line_num);
goto done;
}
......@@ -800,12 +866,18 @@ static int parse_patch_binary_nodata(
git_patch_parsed *patch,
git_patch_parse_ctx *ctx)
{
const char *old = patch->old_path ? patch->old_path : patch->header_old_path;
const char *new = patch->new_path ? patch->new_path : patch->header_new_path;
if (!old || !new)
return git_parse_err("corrupt binary data without paths at line %"PRIuZ, ctx->parse_ctx.line_num);
if (git_parse_advance_expected_str(&ctx->parse_ctx, "Binary files ") < 0 ||
git_parse_advance_expected_str(&ctx->parse_ctx, patch->header_old_path) < 0 ||
git_parse_advance_expected_str(&ctx->parse_ctx, " and ") < 0 ||
git_parse_advance_expected_str(&ctx->parse_ctx, patch->header_new_path) < 0 ||
git_parse_advance_expected_str(&ctx->parse_ctx, " differ") < 0 ||
git_parse_advance_nl(&ctx->parse_ctx) < 0)
git_parse_advance_expected_str(&ctx->parse_ctx, old) < 0 ||
git_parse_advance_expected_str(&ctx->parse_ctx, " and ") < 0 ||
git_parse_advance_expected_str(&ctx->parse_ctx, new) < 0 ||
git_parse_advance_expected_str(&ctx->parse_ctx, " differ") < 0 ||
git_parse_advance_nl(&ctx->parse_ctx) < 0)
return git_parse_err("corrupt git binary header at line %"PRIuZ, ctx->parse_ctx.line_num);
patch->base.binary.contains_data = 0;
......@@ -1038,6 +1110,8 @@ int git_patch_parsed_from_diff(git_patch **out, git_diff *d, size_t idx)
static void patch_parsed__free(git_patch *p)
{
git_patch_parsed *patch = (git_patch_parsed *)p;
git_diff_line *line;
size_t i;
if (!patch)
return;
......@@ -1047,6 +1121,8 @@ static void patch_parsed__free(git_patch *p)
git__free((char *)patch->base.binary.old_file.data);
git__free((char *)patch->base.binary.new_file.data);
git_array_clear(patch->base.hunks);
git_array_foreach(patch->base.lines, i, line)
git__free((char *) line->content);
git_array_clear(patch->base.lines);
git__free(patch->base.delta);
......
......@@ -794,7 +794,7 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *
if (git_buf_joinpath(&ref_path, basedir, name) < 0)
return -1;
filebuf_flags = GIT_FILEBUF_FORCE;
filebuf_flags = GIT_FILEBUF_CREATE_LEADING_DIRS;
if (backend->fsync)
filebuf_flags |= GIT_FILEBUF_FSYNC;
......@@ -2080,6 +2080,9 @@ int git_refdb_backend_fs(
backend = git__calloc(1, sizeof(refdb_fs_backend));
GIT_ERROR_CHECK_ALLOC(backend);
if (git_refdb_init_backend(&backend->parent, GIT_REFDB_BACKEND_VERSION) < 0)
goto fail;
backend->repo = repository;
if (repository->gitdir) {
......
......@@ -140,6 +140,11 @@ int git_reference_delete(git_reference *ref)
const git_oid *old_id = NULL;
const char *old_target = NULL;
if (!strcmp(ref->name, "HEAD")) {
git_error_set(GIT_ERROR_REFERENCE, "cannot delete HEAD");
return GIT_ERROR;
}
if (ref->type == GIT_REFERENCE_DIRECT)
old_id = &ref->target.oid;
else
......
......@@ -799,7 +799,7 @@ int git_repository_open_ext(
unsigned is_worktree;
git_buf gitdir = GIT_BUF_INIT, workdir = GIT_BUF_INIT,
gitlink = GIT_BUF_INIT, commondir = GIT_BUF_INIT;
git_repository *repo;
git_repository *repo = NULL;
git_config *config = NULL;
if (flags & GIT_REPOSITORY_OPEN_FROM_ENV)
......@@ -812,7 +812,7 @@ int git_repository_open_ext(
&gitdir, &workdir, &gitlink, &commondir, start_path, flags, ceiling_dirs);
if (error < 0 || !repo_ptr)
return error;
goto cleanup;
repo = repository_alloc();
GIT_ERROR_CHECK_ALLOC(repo);
......@@ -858,11 +858,14 @@ int git_repository_open_ext(
cleanup:
git_buf_dispose(&gitdir);
git_buf_dispose(&workdir);
git_buf_dispose(&gitlink);
git_buf_dispose(&commondir);
git_config_free(config);
if (error < 0)
git_repository_free(repo);
else
if (repo_ptr)
*repo_ptr = repo;
return error;
......@@ -2473,7 +2476,7 @@ int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head
git_oid_fmt(orig_head_str, orig_head);
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_ORIG_HEAD_FILE)) == 0 &&
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) == 0 &&
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) == 0 &&
(error = git_filebuf_printf(&file, "%.*s\n", GIT_OID_HEXSZ, orig_head_str)) == 0)
error = git_filebuf_commit(&file);
......
......@@ -29,7 +29,7 @@ static int write_revert_head(
int error = 0;
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_REVERT_HEAD_FILE)) >= 0 &&
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_REVERT_FILE_MODE)) >= 0 &&
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_REVERT_FILE_MODE)) >= 0 &&
(error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0)
error = git_filebuf_commit(&file);
......@@ -51,7 +51,7 @@ static int write_merge_msg(
int error = 0;
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 ||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_REVERT_FILE_MODE)) < 0 ||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_REVERT_FILE_MODE)) < 0 ||
(error = git_filebuf_printf(&file, "Revert \"%s\"\n\nThis reverts commit %s.\n",
commit_msgline, commit_oidstr)) < 0)
goto cleanup;
......
......@@ -82,15 +82,25 @@ static int git_sysdir_guess_global_dirs(git_buf *out)
#else
int error;
uid_t uid, euid;
const char *sandbox_id;
uid = getuid();
euid = geteuid();
/**
* If APP_SANDBOX_CONTAINER_ID is set, we are running in a
* sandboxed environment on macOS.
*/
sandbox_id = getenv("APP_SANDBOX_CONTAINER_ID");
/*
* In case we are running setuid, use the configuration
* of the effective user.
*
* If we are running in a sandboxed environment on macOS,
* we have to get the HOME dir from the password entry file.
*/
if (uid == euid)
if (!sandbox_id && uid == euid)
error = git__getenv(out, "HOME");
else
error = get_passwd_home(out, euid);
......
......@@ -339,7 +339,13 @@ int git_transaction_commit(git_transaction *tx)
return error;
}
if (node->ref_type != GIT_REFERENCE_INVALID) {
if (node->ref_type == GIT_REFERENCE_INVALID) {
/* ref was locked but not modified */
if ((error = git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL)) < 0) {
return error;
}
node->committed = true;
} else {
if ((error = update_target(tx->db, node)) < 0)
return error;
}
......
......@@ -273,7 +273,7 @@ static int ok_pkt(git_pkt **out, const char *line, size_t len)
line += 3;
len -= 3;
if (line[len - 1] == '\n')
if (len && line[len - 1] == '\n')
--len;
GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
......
......@@ -48,6 +48,10 @@
# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 0x00000800
#endif
#ifndef HTTP_STATUS_PERMANENT_REDIRECT
# define HTTP_STATUS_PERMANENT_REDIRECT 308
#endif
static const char *prefix_https = "https://";
static const char *upload_pack_service = "upload-pack";
static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
......@@ -1014,7 +1018,8 @@ replay:
HTTP_STATUS_REDIRECT == status_code ||
(HTTP_STATUS_REDIRECT_METHOD == status_code &&
get_verb == s->verb) ||
HTTP_STATUS_REDIRECT_KEEP_VERB == status_code)) {
HTTP_STATUS_REDIRECT_KEEP_VERB == status_code ||
HTTP_STATUS_PERMANENT_REDIRECT == status_code)) {
/* Check for Windows 7. This workaround is only necessary on
* Windows Vista and earlier. Windows 7 is version 6.1. */
......
......@@ -33,7 +33,7 @@ typedef int GIT_SOCKET;
# define st_atime_nsec st_atim.tv_nsec
# define st_mtime_nsec st_mtim.tv_nsec
# define st_ctime_nsec st_ctim.tv_nsec
#elif !defined(GIT_USE_STAT_MTIME_NSEC) && defined(GIT_USE_NEC)
#elif !defined(GIT_USE_STAT_MTIME_NSEC) && defined(GIT_USE_NSEC)
# error GIT_USE_NSEC defined but unknown struct stat nanosecond type
#endif
......
......@@ -32,8 +32,6 @@ static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter)
thread->result = thread->proc(thread->param);
git__free_tls_data();
return CLEAN_THREAD_EXIT;
}
......@@ -103,9 +101,6 @@ void git_thread_exit(void *value)
{
assert(GIT_GLOBAL->current_thread);
GIT_GLOBAL->current_thread->result = value;
git__free_tls_data();
ExitThread(CLEAN_THREAD_EXIT);
}
......
......@@ -131,6 +131,17 @@ void test_apply_fromdiff__lastline(void)
PATCH_ORIGINAL_TO_CHANGE_LASTLINE, NULL));
}
void test_apply_fromdiff__change_middle_and_lastline_nocontext(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_CHANGE_MIDDLE_AND_LASTLINE, "file.txt",
PATCH_ORIGINAL_TO_CHANGE_MIDDLE_AND_LASTLINE_NOCONTEXT, &diff_opts));
}
void test_apply_fromdiff__prepend(void)
{
cl_git_pass(apply_buf(
......@@ -333,3 +344,36 @@ void test_apply_fromdiff__binary_delete(void)
NULL, NULL,
NULL, &binary_opts));
}
void test_apply_fromdiff__patching_correctly_truncates_source(void)
{
git_buf original = GIT_BUF_INIT, patched = GIT_BUF_INIT;
git_patch *patch;
unsigned int mode;
char *path;
cl_git_pass(git_patch_from_buffers(&patch,
"foo\nbar", 7, "file.txt",
"foo\nfoo", 7, "file.txt", NULL));
/*
* Previously, we would fail to correctly truncate the source buffer if
* the source has more than one line and ends with a non-newline
* character. In the following call, we thus truncate the source string
* in the middle of the second line. Without the bug fixed, we would
* successfully apply the patch to the source and return success. With
* the overflow being fixed, we should return an error.
*/
cl_git_fail_with(GIT_EAPPLYFAIL,
git_apply__patch(&patched, &path, &mode,
"foo\nbar\n", 5, patch, NULL));
/* Verify that the patch succeeds if we do not truncate */
cl_git_pass(git_apply__patch(&patched, &path, &mode,
"foo\nbar\n", 7, patch, NULL));
git_buf_dispose(&original);
git_buf_dispose(&patched);
git_patch_free(patch);
git__free(path);
}
#include "clar_libgit2.h"
#include "apply_helpers.h"
#include "../merge/merge_helpers.h"
static git_repository *repo;
......@@ -56,3 +57,38 @@ void test_apply_tree__one(void)
git_commit_free(b_commit);
}
void test_apply_tree__adds_file(void)
{
git_oid a_oid;
git_commit *a_commit;
git_tree *a_tree;
git_diff *diff;
git_index *index = NULL;
struct merge_index_entry expected[] = {
{ 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" },
};
git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707");
cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid));
cl_git_pass(git_commit_tree(&a_tree, a_commit));
cl_git_pass(git_diff_from_buffer(&diff,
DIFF_ADD_FILE, strlen(DIFF_ADD_FILE)));
cl_git_pass(git_apply_to_tree(&index, repo, a_tree, diff, NULL));
merge_test_index(index, expected, 7);
git_index_free(index);
git_diff_free(diff);
git_tree_free(a_tree);
git_commit_free(a_commit);
}
......@@ -17,6 +17,32 @@ void test_blame_buffer__cleanup(void)
git_repository_free(g_repo);
}
void test_blame_buffer__index(void)
{
const git_blame_hunk *hunk;
const char *buffer = "Hello\nWorld!";
/*
* We need to open a different file from the ones used in other tests. Close
* the one opened in test_blame_buffer__initialize() to avoid a leak.
*/
git_blame_free(g_fileblame);
g_fileblame = NULL;
cl_git_pass(git_blame_file(&g_fileblame, g_repo, "file.txt", NULL));
cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
cl_assert_equal_i(2, git_blame_get_hunk_count(g_bufferblame));
check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 1, 0, "836bc00b", "file.txt");
hunk = git_blame_get_hunk_byline(g_bufferblame, 1);
cl_assert(hunk);
cl_assert_equal_s("lhchavez", hunk->final_signature->name);
check_blame_hunk_index(g_repo, g_bufferblame, 1, 2, 1, 0, "00000000", "file.txt");
hunk = git_blame_get_hunk_byline(g_bufferblame, 2);
cl_assert(hunk);
cl_assert(hunk->final_signature == NULL);
}
void test_blame_buffer__added_line(void)
{
const git_blame_hunk *hunk;
......
......@@ -231,9 +231,9 @@ check_buf_append(
git_buf_puts(&tgt, data_b);
cl_assert(git_buf_oom(&tgt) == 0);
cl_assert_equal_s(expected_data, git_buf_cstr(&tgt));
cl_assert(tgt.size == expected_size);
cl_assert_equal_i(tgt.size, expected_size);
if (expected_asize > 0)
cl_assert(tgt.asize == expected_asize);
cl_assert_equal_i(tgt.asize, expected_asize);
git_buf_dispose(&tgt);
}
......@@ -308,7 +308,7 @@ void test_core_buffer__5(void)
REP16("x") REP16("o"), 32, 40);
check_buf_append(test_4096, "", test_4096, 4096, 4104);
check_buf_append(test_4096, test_4096, test_8192, 8192, 9240);
check_buf_append(test_4096, test_4096, test_8192, 8192, 8200);
/* check sequences of appends */
check_buf_append_abc("a", "b", "c",
......@@ -1204,3 +1204,39 @@ void test_core_buffer__dont_grow_borrowed(void)
cl_git_fail_with(GIT_EINVALID, git_buf_grow(&buf, 1024));
}
void test_core_buffer__dont_hit_infinite_loop_when_resizing(void)
{
git_buf buf = GIT_BUF_INIT;
cl_git_pass(git_buf_puts(&buf, "foobar"));
/*
* We do not care whether this succeeds or fails, which
* would depend on platform-specific allocation
* semantics. We only want to know that the function
* actually returns.
*/
(void)git_buf_try_grow(&buf, SIZE_MAX, true);
git_buf_dispose(&buf);
}
void test_core_buffer__avoid_printing_into_oom_buffer(void)
{
git_buf buf = GIT_BUF_INIT;
/* Emulate OOM situation with a previous allocation */
buf.asize = 8;
buf.ptr = git_buf__oom;
/*
* Print the same string again. As the buffer still has
* an `asize` of 8 due to the previous print,
* `ENSURE_SIZE` would not try to reallocate the array at
* all. As it didn't explicitly check for `git_buf__oom`
* in earlier versions, this would've resulted in it
* returning successfully and thus `git_buf_puts` would
* just print into the `git_buf__oom` array.
*/
cl_git_fail(git_buf_puts(&buf, "foobar"));
}
......@@ -78,6 +78,36 @@ void test_diff_parse__exact_rename(void)
git_diff_free(diff);
}
void test_diff_parse__empty_file(void)
{
const char *content =
"---\n"
" file | 0\n"
" 1 file changed, 0 insertions(+), 0 deletions(-)\n"
" created mode 100644 file\n"
"\n"
"diff --git a/file b/file\n"
"new file mode 100644\n"
"index 0000000..e69de29\n"
"-- \n"
"2.20.1\n";
git_diff *diff;
cl_git_pass(git_diff_from_buffer(
&diff, content, strlen(content)));
git_diff_free(diff);
}
void test_diff_parse__no_extended_headers(void)
{
const char *content = PATCH_NO_EXTENDED_HEADERS;
git_diff *diff;
cl_git_pass(git_diff_from_buffer(
&diff, content, strlen(content)));
git_diff_free(diff);
}
void test_diff_parse__invalid_patches_fails(void)
{
test_parse_invalid_diff(PATCH_CORRUPT_MISSING_NEW_FILE);
......
......@@ -620,6 +620,7 @@ void test_iterator_workdir__filesystem2(void)
"heads/subtrees",
"heads/test",
"heads/testrepo-worktree",
"symref",
"tags/e90810b",
"tags/foo/bar",
"tags/foo/foo/bar",
......@@ -632,7 +633,7 @@ void test_iterator_workdir__filesystem2(void)
cl_git_pass(git_iterator_for_filesystem(
&i, "testrepo/.git/refs", NULL));
expect_iterator_items(i, 16, expect_base, 16, expect_base);
expect_iterator_items(i, 17, expect_base, 17, expect_base);
git_iterator_free(i);
}
......
......@@ -27,6 +27,18 @@ static void ensure_patch_validity(git_patch *patch)
cl_assert_equal_i(0, delta->new_file.size);
}
static void ensure_identical_patch_inout(const char *content) {
git_buf buf = GIT_BUF_INIT;
git_patch *patch;
cl_git_pass(git_patch_from_buffer(&patch, content, strlen(content), NULL));
cl_git_pass(git_patch_to_buf(&buf, patch));
cl_assert_equal_strn(git_buf_cstr(&buf), content, strlen(content));
git_patch_free(patch);
git_buf_dispose(&buf);
}
void test_patch_parse__original_to_change_middle(void)
{
git_patch *patch;
......@@ -102,9 +114,106 @@ void test_patch_parse__invalid_patches_fails(void)
strlen(PATCH_CORRUPT_MISSING_HUNK_HEADER), NULL));
}
void test_patch_parse__no_newline_at_end_of_new_file(void)
{
ensure_identical_patch_inout(PATCH_APPEND_NO_NL);
}
void test_patch_parse__no_newline_at_end_of_old_file(void)
{
ensure_identical_patch_inout(PATCH_APPEND_NO_NL_IN_OLD_FILE);
}
void test_patch_parse__files_with_whitespaces_succeeds(void)
{
ensure_identical_patch_inout(PATCH_NAME_WHITESPACE);
}
void test_patch_parse__lifetime_of_patch_does_not_depend_on_buffer(void)
{
git_buf diff = GIT_BUF_INIT, rendered = GIT_BUF_INIT;
git_patch *patch;
cl_git_pass(git_buf_sets(&diff, PATCH_ORIGINAL_TO_CHANGE_MIDDLE));
cl_git_pass(git_patch_from_buffer(&patch, diff.ptr, diff.size, NULL));
git_buf_dispose(&diff);
cl_git_pass(git_patch_to_buf(&rendered, patch));
cl_assert_equal_s(PATCH_ORIGINAL_TO_CHANGE_MIDDLE, rendered.ptr);
git_buf_dispose(&rendered);
cl_git_pass(git_patch_to_buf(&rendered, patch));
cl_assert_equal_s(PATCH_ORIGINAL_TO_CHANGE_MIDDLE, rendered.ptr);
git_buf_dispose(&rendered);
git_patch_free(patch);
}
void test_patch_parse__binary_file_with_missing_paths(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_MISSING_PATHS,
strlen(PATCH_BINARY_FILE_WITH_MISSING_PATHS), NULL));
}
void test_patch_parse__binary_file_with_whitespace_paths(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_WHITESPACE_PATHS,
strlen(PATCH_BINARY_FILE_WITH_WHITESPACE_PATHS), NULL));
}
void test_patch_parse__binary_file_with_empty_quoted_paths(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_QUOTED_EMPTY_PATHS,
strlen(PATCH_BINARY_FILE_WITH_QUOTED_EMPTY_PATHS), NULL));
}
void test_patch_parse__binary_file_path_with_spaces(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_PATH_WITH_SPACES,
strlen(PATCH_BINARY_FILE_PATH_WITH_SPACES), NULL));
}
void test_patch_parse__binary_file_path_without_body_paths(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_PATH_WITHOUT_BODY_PATHS,
strlen(PATCH_BINARY_FILE_PATH_WITHOUT_BODY_PATHS), NULL));
}
void test_patch_parse__binary_file_with_truncated_delta(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_TRUNCATED_DELTA,
strlen(PATCH_BINARY_FILE_WITH_TRUNCATED_DELTA), NULL));
cl_assert_equal_s(git_error_last()->message, "truncated binary data at line 5");
}
void test_patch_parse__memory_leak_on_multiple_paths(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch, PATCH_MULTIPLE_OLD_PATHS, strlen(PATCH_MULTIPLE_OLD_PATHS), NULL));
}
void test_patch_parse__truncated_no_newline_at_end_of_file(void)
{
size_t len = strlen(PATCH_APPEND_NO_NL) - strlen("at end of file\n");
const git_diff_line *line;
git_patch *patch;
cl_git_pass(git_patch_from_buffer(&patch, PATCH_APPEND_NO_NL, len, NULL));
cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 4));
cl_assert_equal_s(line->content, "\\ No newline ");
git_patch_free(patch);
}
void test_patch_parse__line_number_overflow(void)
{
git_patch *patch;
cl_git_pass(git_patch_from_buffer(&patch, PATCH_NAME_WHITESPACE, strlen(PATCH_NAME_WHITESPACE), NULL));
cl_git_fail(git_patch_from_buffer(&patch, PATCH_INTMAX_NEW_LINES, strlen(PATCH_INTMAX_NEW_LINES), NULL));
git_patch_free(patch);
}
......@@ -263,6 +263,32 @@
"-(this line is changed)\n" \
"+(THIS line is changed!)\n"
/* A change in the middle and a deletion of the newline at the end of the file */
#define FILE_CHANGE_MIDDLE_AND_LASTLINE \
"hey!\n" \
"this is some context!\n" \
"around some lines\n" \
"that will change\n" \
"yes it is!\n" \
"(THIS line is changed!)\n" \
"and this\n" \
"is additional context\n" \
"BELOW it! - (THIS line is changed!)"
#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_AND_LASTLINE_NOCONTEXT \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..e05d36c 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -6 +6 @@ yes it is!\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n" \
"@@ -9 +9 @@ is additional context\n" \
"-below it!\n" \
"+BELOW it! - (THIS line is changed!)\n" \
"\\ No newline at end of file\n"
/* A deletion at the beginning of the file and a change in the middle */
#define FILE_DELETE_AND_CHANGE \
......@@ -681,6 +707,16 @@
"+added line with no nl\n" \
"\\ No newline at end of file\n"
#define PATCH_APPEND_NO_NL_IN_OLD_FILE \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..83759c0 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -1,1 +1,1 @@\n" \
"-foo\n" \
"\\ No newline at end of file\n" \
"+foo\n"
#define PATCH_NAME_WHITESPACE \
"diff --git a/file with spaces.txt b/file with spaces.txt\n" \
"index 9432026..83759c0 100644\n" \
......@@ -859,3 +895,65 @@
"+++ b/test-file\r\n" \
"@@ -0,0 +1 @@\r\n" \
"+a contents\r\n"
#define PATCH_NO_EXTENDED_HEADERS \
"diff --git a/file b/file\n" \
"--- a/file\n" \
"+++ b/file\n" \
"@@ -1,3 +1,3 @@\n" \
" a\n" \
"-b\n" \
"+bb\n" \
" c\n"
#define PATCH_BINARY_FILE_WITH_MISSING_PATHS \
"diff --git \n" \
"--- \n" \
"+++ \n" \
"Binary files "
#define PATCH_BINARY_FILE_WITH_WHITESPACE_PATHS \
"diff --git a/file b/file\n" \
"--- \n" \
"+++ \n" \
"Binary files "
#define PATCH_BINARY_FILE_WITH_QUOTED_EMPTY_PATHS \
"diff --git a/file b/file\n" \
"--- \"\"\n" \
"+++ \"\"\n" \
"Binary files "
#define PATCH_BINARY_FILE_PATH_WITH_SPACES \
"diff --git a b c d e f\n" \
"--- a b c\n" \
"+++ d e f\n" \
"Binary files a b c and d e f differ"
#define PATCH_BINARY_FILE_PATH_WITHOUT_BODY_PATHS \
"diff --git a b c d e f\n" \
"--- \n" \
"+++ \n" \
"Binary files a b c and d e f differ"
#define PATCH_BINARY_FILE_WITH_TRUNCATED_DELTA \
"diff --git a/file b/file\n" \
"index 1420..b71f\n" \
"GIT binary patch\n" \
"delta 7\n" \
"d"
#define PATCH_MULTIPLE_OLD_PATHS \
"diff --git \n" \
"--- \n" \
"+++ \n" \
"index 0000..7DDb\n" \
"--- \n"
#define PATCH_INTMAX_NEW_LINES \
"diff --git a/file b/file\n" \
"--- a/file\n" \
"+++ b/file\n" \
"@@ -0 +2147483647 @@\n" \
"\n" \
" "
......@@ -105,3 +105,14 @@ void test_refs_delete__remove(void)
cl_git_fail(git_reference_lookup(&ref, g_repo, packed_test_head_name));
}
void test_refs_delete__head(void)
{
git_reference *ref;
/* Check that it is not possible to delete HEAD */
cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
cl_git_fail(git_reference_delete(ref));
git_reference_free(ref);
}
......@@ -36,7 +36,7 @@ void test_refs_list__all(void)
/* We have exactly 12 refs in total if we include the packed ones:
* there is a reference that exists both in the packfile and as
* loose, but we only list it once */
cl_assert_equal_i((int)ref_list.count, 18);
cl_assert_equal_i((int)ref_list.count, 19);
git_strarray_free(&ref_list);
}
......@@ -51,7 +51,7 @@ void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_exten
"144344043ba4d4a405da03de3844aa829ae8be0e\n");
cl_git_pass(git_reference_list(&ref_list, g_repo));
cl_assert_equal_i((int)ref_list.count, 18);
cl_assert_equal_i((int)ref_list.count, 19);
git_strarray_free(&ref_list);
}
......@@ -74,8 +74,8 @@ void test_refs_races__delete(void)
git_reference_free(ref);
/* We cannot delete a symbolic value that doesn't match */
cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
cl_git_pass(git_reference_symbolic_create_matching(&ref2, g_repo, "HEAD", other_refname, 1, NULL, refname));
cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref"));
cl_git_pass(git_reference_symbolic_create_matching(&ref2, g_repo, "refs/symref", other_refname, 1, NULL, refname));
cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref));
git_reference_free(ref);
......@@ -131,19 +131,19 @@ void test_refs_races__switch_symbolic_to_oid(void)
git_oid_fromstr(&other_id, other_commit_id);
/* Removing a symbolic ref when it's currently direct should fail */
cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
cl_git_pass(git_reference_create(&ref2, g_repo, "HEAD", &id, 1, NULL));
cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref"));
cl_git_pass(git_reference_create(&ref2, g_repo, "refs/symref", &id, 1, NULL));
cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref));
git_reference_free(ref);
git_reference_free(ref2);
cl_git_pass(git_reference_symbolic_create(&ref, g_repo, "HEAD", refname, 1, NULL));
cl_git_pass(git_reference_symbolic_create(&ref, g_repo, "refs/symref", refname, 1, NULL));
git_reference_free(ref);
/* Updating a symbolic ref when it's currently direct should fail */
cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
cl_git_pass(git_reference_create(&ref2, g_repo, "HEAD", &id, 1, NULL));
cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref"));
cl_git_pass(git_reference_create(&ref2, g_repo, "refs/symref", &id, 1, NULL));
cl_git_fail_with(GIT_EMODIFIED, git_reference_symbolic_set_target(&ref3, ref, other_refname, NULL));
git_reference_free(ref);
......
......@@ -108,3 +108,50 @@ void test_refs_transactions__unlocked_set(void)
cl_git_fail_with(GIT_ENOTFOUND, git_transaction_set_target(g_tx, "refs/heads/foo", &id, NULL, NULL));
cl_git_pass(git_transaction_commit(g_tx));
}
void test_refs_transactions__error_on_locking_locked_ref(void)
{
git_oid id;
git_transaction *g_tx_with_lock;
git_repository *g_repo_with_locking_tx;
const char *g_repo_path = git_repository_path(g_repo);
/* prepare a separate transaction in another instance of testrepo and lock master */
cl_git_pass(git_repository_open(&g_repo_with_locking_tx, g_repo_path));
cl_git_pass(git_transaction_new(&g_tx_with_lock, g_repo_with_locking_tx));
cl_git_pass(git_transaction_lock_ref(g_tx_with_lock, "refs/heads/master"));
/* lock reference for set_target */
cl_git_pass(git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"));
cl_git_fail_with(GIT_ELOCKED, git_transaction_lock_ref(g_tx, "refs/heads/master"));
cl_git_fail_with(GIT_ENOTFOUND, git_transaction_set_target(g_tx, "refs/heads/master", &id, NULL, NULL));
git_transaction_free(g_tx_with_lock);
git_repository_free(g_repo_with_locking_tx);
}
void test_refs_transactions__commit_unlocks_unmodified_ref(void)
{
git_transaction *second_tx;
cl_git_pass(git_transaction_new(&second_tx, g_repo));
cl_git_pass(git_transaction_lock_ref(second_tx, "refs/heads/master"));
cl_git_pass(git_transaction_commit(second_tx));
/* a transaction must now be able to get the lock */
cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master"));
git_transaction_free(second_tx);
}
void test_refs_transactions__free_unlocks_unmodified_ref(void)
{
git_transaction *second_tx;
cl_git_pass(git_transaction_new(&second_tx, g_repo));
cl_git_pass(git_transaction_lock_ref(second_tx, "refs/heads/master"));
git_transaction_free(second_tx);
/* a transaction must now be able to get the lock */
cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master"));
}
......@@ -88,6 +88,17 @@ void test_repo_open__open_with_discover(void)
cl_fixture_cleanup("attr");
}
void test_repo_open__check_if_repository(void)
{
cl_git_sandbox_init("empty_standard_repo");
/* Pass NULL for the output parameter to check for but not open the repo */
cl_git_pass(git_repository_open_ext(NULL, "empty_standard_repo", 0, NULL));
cl_git_fail(git_repository_open_ext(NULL, "repo_does_not_exist", 0, NULL));
cl_fixture_cleanup("empty_standard_repo");
}
static void make_gitlink_dir(const char *dir, const char *linktext)
{
git_buf path = GIT_BUF_INIT;
......
6653ff42313eb5c82806f145391b18a9699800c7
836bc00b06cb60eb0f629e237ad2b58adb2cfc7e
......@@ -1213,3 +1213,47 @@ void test_status_ignore__unignored_subdirs(void)
assert_is_ignored("dir/a.test");
refute_is_ignored("dir/subdir/a.test");
}
void test_status_ignore__override_nested_wildcard_unignore(void)
{
git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
git_status_list *statuslist;
git_status_options opts = GIT_STATUS_OPTIONS_INIT;
const git_status_entry *status;
cl_git_pass(git_futils_mkdir_r("empty_standard_repo/dir", 0777));
cl_git_pass(git_futils_mkdir_r("empty_standard_repo/dir/subdir", 0777));
cl_git_mkfile("empty_standard_repo/.gitignore", "a.test\n");
cl_git_mkfile("empty_standard_repo/dir/.gitignore", "!*.test\n");
cl_git_mkfile("empty_standard_repo/dir/subdir/.gitignore", "a.test\n");
cl_git_mkfile("empty_standard_repo/dir/a.test", "pong");
cl_git_mkfile("empty_standard_repo/dir/subdir/a.test", "pong");
opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
cl_git_pass(git_status_list_new(&statuslist, repo, &opts));
cl_assert_equal_sz(4, git_status_list_entrycount(statuslist));
status = git_status_byindex(statuslist, 0);
cl_assert(status != NULL);
cl_assert_equal_s(".gitignore", status->index_to_workdir->old_file.path);
cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status);
status = git_status_byindex(statuslist, 1);
cl_assert(status != NULL);
cl_assert_equal_s("dir/.gitignore", status->index_to_workdir->old_file.path);
cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status);
status = git_status_byindex(statuslist, 2);
cl_assert(status != NULL);
cl_assert_equal_s("dir/a.test", status->index_to_workdir->old_file.path);
cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status);
status = git_status_byindex(statuslist, 3);
cl_assert(status != NULL);
cl_assert_equal_s("dir/subdir/.gitignore", status->index_to_workdir->old_file.path);
cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status);
git_status_list_free(statuslist);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment