Merge branch 'development' into blame_rebased

Conflicts:
	include/git2.h
This commit is contained in:
Ben Straub 2013-09-16 16:12:31 -07:00
commit 549931679a
264 changed files with 14993 additions and 3760 deletions

View File

@ -1,7 +1,6 @@
# Travis-CI Build for libgit2 # Travis-CI Build for libgit2
# see travis-ci.org for details # see travis-ci.org for details
# As CMake is not officially supported we use erlang VMs
language: c language: c
compiler: compiler:
@ -18,26 +17,18 @@ matrix:
- compiler: i586-mingw32msvc-gcc - compiler: i586-mingw32msvc-gcc
env: OPTIONS="-DBUILD_CLAR=OFF -DWIN32=ON -DMINGW=ON" env: OPTIONS="-DBUILD_CLAR=OFF -DWIN32=ON -DMINGW=ON"
# Make sure CMake is installed
install: install:
- sudo apt-get update >/dev/null - sudo apt-get -qq update
- sudo apt-get -q install cmake valgrind - sudo apt-get -qq install cmake libssh2-1-dev openssh-client openssh-server
# Run the Build script # Run the Build script and tests
script: script:
- mkdir _temp - script/cibuild.sh
- git init --bare _temp/test.git
- git daemon --listen=localhost --export-all --enable=receive-pack --base-path=_temp _temp 2>/dev/null &
- export GITTEST_REMOTE_URL="git://localhost/test.git"
- mkdir _build
- cd _build
- cmake .. -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS
- cmake --build . --target install
- ctest -V .
# Run Tests # Run Tests
after_success: after_success:
- valgrind --leak-check=full --show-reachable=yes --suppressions=../libgit2_clar.supp ./libgit2_clar -ionline - sudo apt-get -qq install valgrind
- valgrind --leak-check=full --show-reachable=yes --suppressions=./libgit2_clar.supp _build/libgit2_clar -ionline
# Only watch the development branch # Only watch the development branch
branches: branches:

View File

@ -29,17 +29,16 @@ OPTION( PROFILE "Generate profiling information" OFF )
OPTION( ENABLE_TRACE "Enables tracing support" OFF ) OPTION( ENABLE_TRACE "Enables tracing support" OFF )
OPTION( LIBGIT2_FILENAME "Name of the produced binary" OFF ) OPTION( LIBGIT2_FILENAME "Name of the produced binary" OFF )
OPTION( ANDROID "Build for android NDK" OFF )
IF(MSVC) IF(MSVC)
# This option is only availalbe when building with MSVC. By default, # This option is only available when building with MSVC. By default, libgit2
# libgit2 is build using the stdcall calling convention, as that's what # is build using the cdecl calling convention, which is useful if you're
# the CLR expects by default and how the Windows API is built. # writing C. However, the CLR and Win32 API both expect stdcall.
# #
# If you are writing a C or C++ program and want to link to libgit2, you # If you are writing a CLR program and want to link to libgit2, you'll want
# have to either: # to turn this on by invoking CMake with the "-DSTDCALL=ON" argument.
# - Add /Gz to the compiler options of _your_ program / library. OPTION( STDCALL "Build libgit2 with the __stdcall convention" OFF )
# - Turn this off by invoking CMake with the "-DSTDCALL=Off" argument.
#
OPTION( STDCALL "Build libgit2 with the __stdcall convention" ON )
# This option must match the settings used in your program, in particular if you # This option must match the settings used in your program, in particular if you
# are linking statically # are linking statically
@ -110,7 +109,7 @@ ELSE ()
ELSE() ELSE()
MESSAGE("http-parser was not found or is too old; using bundled 3rd-party sources.") MESSAGE("http-parser was not found or is too old; using bundled 3rd-party sources.")
INCLUDE_DIRECTORIES(deps/http-parser) INCLUDE_DIRECTORIES(deps/http-parser)
FILE(GLOB SRC_HTTP deps/http-parser/*.c) FILE(GLOB SRC_HTTP deps/http-parser/*.c deps/http-parser/*.h)
ENDIF() ENDIF()
ENDIF() ENDIF()
@ -130,30 +129,28 @@ IF (ENABLE_TRACE STREQUAL "ON")
ENDIF() ENDIF()
# Include POSIX regex when it is required # Include POSIX regex when it is required
IF(WIN32 OR AMIGA) IF(WIN32 OR AMIGA OR ANDROID)
INCLUDE_DIRECTORIES(deps/regex) INCLUDE_DIRECTORIES(deps/regex)
SET(SRC_REGEX deps/regex/regex.c) SET(SRC_REGEX deps/regex/regex.c)
ENDIF() ENDIF()
# Optional external dependency: zlib # Optional external dependency: zlib
IF(NOT ZLIB_LIBRARY)
# It's optional, but FIND_PACKAGE gives a warning that looks more like an # It's optional, but FIND_PACKAGE gives a warning that looks more like an
# error. # error.
FIND_PACKAGE(ZLIB QUIET) FIND_PACKAGE(ZLIB QUIET)
ENDIF()
IF (ZLIB_FOUND) IF (ZLIB_FOUND)
INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS})
LINK_LIBRARIES(${ZLIB_LIBRARIES}) LINK_LIBRARIES(${ZLIB_LIBRARIES})
# Fake the message CMake would have shown # Fake the message CMake would have shown
MESSAGE("-- Found zlib: ${ZLIB_LIBRARY}") MESSAGE("-- Found zlib: ${ZLIB_LIBRARY}")
ELSEIF (NOT ZLIB_LIBRARY) ELSE()
MESSAGE( "zlib was not found; using bundled 3rd-party sources." ) MESSAGE( "zlib was not found; using bundled 3rd-party sources." )
INCLUDE_DIRECTORIES(deps/zlib) INCLUDE_DIRECTORIES(deps/zlib)
ADD_DEFINITIONS(-DNO_VIZ -DSTDC -DNO_GZIP) ADD_DEFINITIONS(-DNO_VIZ -DSTDC -DNO_GZIP)
FILE(GLOB SRC_ZLIB deps/zlib/*.c) FILE(GLOB SRC_ZLIB deps/zlib/*.c deps/zlib/*.h)
ENDIF() ENDIF()
IF(NOT LIBSSH2_LIBRARY) IF (NOT MINGW)
FIND_PACKAGE(LIBSSH2 QUIET) FIND_PACKAGE(LIBSSH2 QUIET)
ENDIF() ENDIF()
IF (LIBSSH2_FOUND) IF (LIBSSH2_FOUND)
@ -162,6 +159,7 @@ IF (LIBSSH2_FOUND)
SET(SSH_LIBRARIES ${LIBSSH2_LIBRARIES}) SET(SSH_LIBRARIES ${LIBSSH2_LIBRARIES})
ENDIF() ENDIF()
# Platform specific compilation flags # Platform specific compilation flags
IF (MSVC) IF (MSVC)
@ -287,19 +285,19 @@ ENDIF()
ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64) ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64)
# Collect sourcefiles # Collect sourcefiles
FILE(GLOB SRC_H include/git2/*.h) FILE(GLOB SRC_H include/git2.h include/git2/*.h include/git2/sys/*.h)
# On Windows use specific platform sources # On Windows use specific platform sources
IF (WIN32 AND NOT CYGWIN) IF (WIN32 AND NOT CYGWIN)
ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0501) ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0501)
FILE(GLOB SRC_OS src/win32/*.c) FILE(GLOB SRC_OS src/win32/*.c src/win32/*.h)
ELSEIF (AMIGA) ELSEIF (AMIGA)
ADD_DEFINITIONS(-DNO_ADDRINFO -DNO_READDIR_R) ADD_DEFINITIONS(-DNO_ADDRINFO -DNO_READDIR_R)
FILE(GLOB SRC_OS src/amiga/*.c) FILE(GLOB SRC_OS src/amiga/*.c src/amiga/*.h)
ELSE() ELSE()
FILE(GLOB SRC_OS src/unix/*.c) FILE(GLOB SRC_OS src/unix/*.c src/unix/*.h)
ENDIF() ENDIF()
FILE(GLOB SRC_GIT2 src/*.c src/transports/*.c src/xdiff/*.c) FILE(GLOB SRC_GIT2 src/*.c src/*.h src/transports/*.c src/transports/*.h src/xdiff/*.c src/xdiff/*.h)
# Determine architecture of the machine # Determine architecture of the machine
IF (CMAKE_SIZEOF_VOID_P EQUAL 8) IF (CMAKE_SIZEOF_VOID_P EQUAL 8)
@ -311,7 +309,7 @@ ELSE()
ENDIF() ENDIF()
# Compile and link libgit2 # Compile and link libgit2
ADD_LIBRARY(git2 ${SRC_GIT2} ${SRC_OS} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1} ${WIN_RC}) ADD_LIBRARY(git2 ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1} ${WIN_RC})
TARGET_LINK_LIBRARIES(git2 ${SSL_LIBRARIES}) TARGET_LINK_LIBRARIES(git2 ${SSL_LIBRARIES})
TARGET_LINK_LIBRARIES(git2 ${SSH_LIBRARIES}) TARGET_LINK_LIBRARIES(git2 ${SSH_LIBRARIES})
TARGET_OS_LIBRARIES(git2) TARGET_OS_LIBRARIES(git2)
@ -361,12 +359,12 @@ IF (BUILD_CLAR)
ADD_DEFINITIONS(-DCLAR_RESOURCES=\"${TEST_RESOURCES}\") ADD_DEFINITIONS(-DCLAR_RESOURCES=\"${TEST_RESOURCES}\")
INCLUDE_DIRECTORIES(${CLAR_PATH}) INCLUDE_DIRECTORIES(${CLAR_PATH})
FILE(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c) FILE(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c ${CLAR_PATH}/*/*.h)
SET(SRC_CLAR "${CLAR_PATH}/main.c" "${CLAR_PATH}/clar_libgit2.c" "${CLAR_PATH}/clar.c") SET(SRC_CLAR "${CLAR_PATH}/main.c" "${CLAR_PATH}/clar_libgit2.c" "${CLAR_PATH}/clar.c")
ADD_CUSTOM_COMMAND( ADD_CUSTOM_COMMAND(
OUTPUT ${CLAR_PATH}/clar.suite OUTPUT ${CLAR_PATH}/clar.suite
COMMAND ${PYTHON_EXECUTABLE} generate.py -f -xonline . COMMAND ${PYTHON_EXECUTABLE} generate.py -f -xonline -xstress .
DEPENDS ${SRC_TEST} DEPENDS ${SRC_TEST}
WORKING_DIRECTORY ${CLAR_PATH} WORKING_DIRECTORY ${CLAR_PATH}
) )
@ -375,7 +373,7 @@ IF (BUILD_CLAR)
${CLAR_PATH}/clar.c ${CLAR_PATH}/clar.c
PROPERTIES OBJECT_DEPENDS ${CLAR_PATH}/clar.suite) PROPERTIES OBJECT_DEPENDS ${CLAR_PATH}/clar.suite)
ADD_EXECUTABLE(libgit2_clar ${SRC_GIT2} ${SRC_OS} ${SRC_CLAR} ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1}) ADD_EXECUTABLE(libgit2_clar ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_CLAR} ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1})
TARGET_LINK_LIBRARIES(libgit2_clar ${SSL_LIBRARIES}) TARGET_LINK_LIBRARIES(libgit2_clar ${SSL_LIBRARIES})
TARGET_LINK_LIBRARIES(libgit2_clar ${SSH_LIBRARIES}) TARGET_LINK_LIBRARIES(libgit2_clar ${SSH_LIBRARIES})
@ -411,23 +409,5 @@ IF (TAGS)
ENDIF () ENDIF ()
IF (BUILD_EXAMPLES) IF (BUILD_EXAMPLES)
FILE(GLOB_RECURSE EXAMPLE_SRC examples/network/*.c) ADD_SUBDIRECTORY(examples)
ADD_EXECUTABLE(cgit2 ${EXAMPLE_SRC})
IF(WIN32)
TARGET_LINK_LIBRARIES(cgit2 git2)
ELSE()
TARGET_LINK_LIBRARIES(cgit2 git2 pthread)
ENDIF()
ADD_EXECUTABLE(git-diff examples/diff.c)
TARGET_LINK_LIBRARIES(git-diff git2)
ADD_EXECUTABLE(git-general examples/general.c)
TARGET_LINK_LIBRARIES(git-general git2)
ADD_EXECUTABLE(git-showindex examples/showindex.c)
TARGET_LINK_LIBRARIES(git-showindex git2)
ADD_EXECUTABLE(git-rev-list examples/rev-list.c)
TARGET_LINK_LIBRARIES(git-rev-list git2)
ENDIF () ENDIF ()

View File

@ -48,6 +48,12 @@ Please include a nice description of your changes with your PR; if we have
to read the whole diff to figure out why you're contributing in the first to read the whole diff to figure out why you're contributing in the first
place, you're less likely to get feedback and have your change merged in. place, you're less likely to get feedback and have your change merged in.
If you are working on a particular area then feel free to submit a PR that
highlights your work in progress (and flag in the PR title that it's not
ready to merge). This will help in getting visibility for your fix, allow
others to comment early on the changes and also let others know that you
are currently working on something.
## Porting Code From Other Open-Source Projects ## Porting Code From Other Open-Source Projects
`libgit2` is licensed under the terms of the GPL v2 with a linking `libgit2` is licensed under the terms of the GPL v2 with a linking
@ -57,14 +63,17 @@ The most common case is porting code from core Git. Git is a pure GPL
project, which means that in order to port code to this project, we need the project, which means that in order to port code to this project, we need the
explicit permission of the author. Check the explicit permission of the author. Check the
[`git.git-authors`](https://github.com/libgit2/libgit2/blob/development/git.git-authors) [`git.git-authors`](https://github.com/libgit2/libgit2/blob/development/git.git-authors)
file for authors who have already consented; feel free to add someone if file for authors who have already consented.
you've obtained their consent.
Other licenses have other requirements; check the license of the library Other licenses have other requirements; check the license of the library
you're porting code *from* to see what you need to do. As a general rule, you're porting code *from* to see what you need to do. As a general rule,
MIT and BSD (3-clause) licenses are typically no problem. Apache 2.0 MIT and BSD (3-clause) licenses are typically no problem. Apache 2.0
license typically doesn't work due to GPL incompatibility. license typically doesn't work due to GPL incompatibility.
If you are pulling in code from core Git, another project or code you've pulled from
a forum / Stack Overflow then please flag this in your PR and also make sure you've
given proper credit to the original author in the code snippet.
## Style Guide ## Style Guide
`libgit2` is written in [ANSI C](http://en.wikipedia.org/wiki/ANSI_C) `libgit2` is written in [ANSI C](http://en.wikipedia.org/wiki/ANSI_C)

63
COPYING
View File

@ -928,3 +928,66 @@ necessary. Here is a sample; alter the names:
Ty Coon, President of Vice Ty Coon, President of Vice
That's all there is to it! That's all there is to it!
----------------------------------------------------------------------
Portions of src/win32/posix_w32.c are derrived from link_win32.c in PHP:
--------------------------------------------------------------------
The PHP License, version 3.01
Copyright (c) 1999 - 2012 The PHP Group. All rights reserved.
--------------------------------------------------------------------
Redistribution and use in source and binary forms, with or without
modification, is permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
3. The name "PHP" must not be used to endorse or promote products
derived from this software without prior written permission. For
written permission, please contact group@php.net.
4. Products derived from this software may not be called "PHP", nor
may "PHP" appear in their name, without prior written permission
from group@php.net. You may indicate that your software works in
conjunction with PHP by saying "Foo for PHP" instead of calling
it "PHP Foo" or "phpfoo"
5. The PHP Group may publish revised and/or new versions of the
license from time to time. Each version will be given a
distinguishing version number.
Once covered code has been published under a particular version
of the license, you may always continue to use it under the terms
of that version. You may also choose to use such covered code
under the terms of any subsequent version of the license
published by the PHP Group. No one other than the PHP Group has
the right to modify the terms applicable to covered code created
under this License.
6. Redistributions of any form whatsoever must retain the following
acknowledgment:
"This product includes PHP software, freely available from
<http://www.php.net/software/>".
THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND
ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP
DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------

View File

@ -1,15 +1,33 @@
PLATFORM=$(shell uname -o) PLATFORM=$(shell uname -s)
ifneq (,$(CROSS_COMPILE))
PREFIX=$(CROSS_COMPILE)-
else
PREFIX=
endif
MINGW=0
ifneq (,$(findstring MINGW32,$(PLATFORM)))
MINGW=1
endif
ifneq (,$(findstring mingw,$(CROSS_COMPILE)))
MINGW=1
endif
rm=rm -f rm=rm -f
AR=ar cq AR=$(PREFIX)ar cq
RANLIB=ranlib RANLIB=$(PREFIX)ranlib
LIBNAME=libgit2.a LIBNAME=libgit2.a
ifeq ($(PLATFORM),Msys)
ifeq ($(MINGW),1)
CC=gcc CC=gcc
else else
CC=cc CC=cc
endif endif
CC:=$(PREFIX)$(CC)
INCLUDES= -I. -Isrc -Iinclude -Ideps/http-parser -Ideps/zlib 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) DEFINES= $(INCLUDES) -DNO_VIZ -DSTDC -DNO_GZIP -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE $(EXTRA_DEFINES)
@ -17,10 +35,10 @@ CFLAGS= -g $(DEFINES) -Wall -Wextra -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 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
ifeq ($(PLATFORM),Msys) ifeq ($(MINGW),1)
SRCS += $(wildcard src/win32/*.c) $(wildcard src/compat/*.c) deps/regex/regex.c SRCS += $(wildcard src/win32/*.c) $(wildcard src/compat/*.c) deps/regex/regex.c
INCLUDES += -Ideps/regex INCLUDES += -Ideps/regex
DEFINES += -DWIN32 -D_WIN32_WINNT=0x0501 DEFINES += -DWIN32 -D_WIN32_WINNT=0x0501 -D__USE_MINGW_ANSI_STDIO=1
else else
SRCS += $(wildcard src/unix/*.c) SRCS += $(wildcard src/unix/*.c)
CFLAGS += -fPIC CFLAGS += -fPIC

View File

@ -11,20 +11,24 @@ libgit2 is licensed under a **very permissive license** (GPLv2 with a special Li
This basically means that you can link it (unmodified) with any kind of software without having to This basically means that you can link it (unmodified) with any kind of software without having to
release its source code. release its source code.
* Mailing list: ~~<libgit2@librelist.org>~~
The libgit2 mailing list has
traditionally been hosted in Librelist, but Librelist is and has always
been a shitshow. We encourage you to [open an issue](https://github.com/libgit2/libgit2/issues)
on GitHub instead for any questions regarding the library.
* Archives: <http://librelist.com/browser/libgit2/>
* Website: <http://libgit2.github.com> * Website: <http://libgit2.github.com>
* StackOverflow Tag: [libgit2](http://stackoverflow.com/questions/tagged/libgit2)
* Issues: <https://github.com/libgit2/libgit2/issues>
* API documentation: <http://libgit2.github.com/libgit2> * API documentation: <http://libgit2.github.com/libgit2>
* IRC: #libgit2 on irc.freenode.net. * IRC: #libgit2 on irc.freenode.net.
* Mailing list: The libgit2 mailing list was
traditionally hosted in Librelist but has been deprecated. We encourage you to
[use StackOverflow](http://stackoverflow.com/questions/tagged/libgit2) instead for any questions regarding
the library, or [open an issue](https://github.com/libgit2/libgit2/issues)
on GitHub for bug reports. The mailing list archives are still available at
<http://librelist.com/browser/libgit2/>.
What It Can Do What It Can Do
================================== ==================================
libgit2 is already very usable. libgit2 is already very usable and is being used in production for many applications including the GitHub.com site, in Plastic SCM
and also powering Microsoft's Visual Studio tools for Git. The library provides:
* SHA conversions, formatting and shortening * SHA conversions, formatting and shortening
* abstracted ODB backend system * abstracted ODB backend system
@ -104,6 +108,28 @@ See [the wiki]
(https://github.com/libgit2/libgit2/wiki/Building-libgit2-on-Windows) (https://github.com/libgit2/libgit2/wiki/Building-libgit2-on-Windows)
for more detailed instructions. for more detailed instructions.
Android
-------
Extract toolchain from NDK using, `make-standalone-toolchain.sh` script.
Optionaly, crosscompile and install OpenSSL inside of it. Then create CMake
toolchain file that configures paths to your crosscompiler (substitude `{PATH}`
with full path to the toolchain):
SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_VERSION Android)
SET(CMAKE_C_COMPILER {PATH}/bin/arm-linux-androideabi-gcc)
SET(CMAKE_CXX_COMPILER {PATH}/bin/arm-linux-androideabi-g++)
SET(CMAKE_FIND_ROOT_PATH {PATH}/sysroot/)
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
Add `-DCMAKE_TOOLCHAIN_FILE={pathToToolchainFile} -DANDROID=1` to cmake command
when configuring.
Language Bindings Language Bindings
================================== ==================================
@ -118,9 +144,9 @@ Here are the bindings to libgit2 that are currently available:
* Delphi * Delphi
* GitForDelphi <https://github.com/libgit2/GitForDelphi> * GitForDelphi <https://github.com/libgit2/GitForDelphi>
* Erlang * Erlang
* Geef <https://github.com/schacon/geef> * Geef <https://github.com/carlosmn/geef>
* Go * Go
* go-git <https://github.com/str1ngs/go-git> * git2go <https://github.com/libgit2/git2go>
* GObject * GObject
* libgit2-glib <https://live.gnome.org/Libgit2-glib> * libgit2-glib <https://live.gnome.org/Libgit2-glib>
* Haskell * Haskell
@ -128,8 +154,8 @@ Here are the bindings to libgit2 that are currently available:
* Lua * Lua
* luagit2 <https://github.com/libgit2/luagit2> * luagit2 <https://github.com/libgit2/luagit2>
* .NET * .NET
* libgit2net, low level bindings <https://github.com/txdv/libgit2net>
* libgit2sharp <https://github.com/libgit2/libgit2sharp> * libgit2sharp <https://github.com/libgit2/libgit2sharp>
* libgit2net, low level bindings superceeded by libgit2sharp <https://github.com/txdv/libgit2net>
* Node.js * Node.js
* node-gitteh <https://github.com/libgit2/node-gitteh> * node-gitteh <https://github.com/libgit2/node-gitteh>
* nodegit <https://github.com/tbranyen/nodegit> * nodegit <https://github.com/tbranyen/nodegit>

14
examples/CMakeLists.txt Normal file
View File

@ -0,0 +1,14 @@
FILE(GLOB_RECURSE SRC_EXAMPLE_GIT2 network/*.c network/*.h)
ADD_EXECUTABLE(cgit2 ${SRC_EXAMPLE_GIT2})
IF(WIN32 OR ANDROID)
TARGET_LINK_LIBRARIES(cgit2 git2)
ELSE()
TARGET_LINK_LIBRARIES(cgit2 git2 pthread)
ENDIF()
FILE(GLOB SRC_EXAMPLE_APPS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c)
FOREACH(src_app ${SRC_EXAMPLE_APPS})
STRING(REPLACE ".c" "" app_name ${src_app})
ADD_EXECUTABLE(${app_name} ${src_app})
TARGET_LINK_LIBRARIES(${app_name} git2)
ENDFOREACH()

View File

@ -3,7 +3,7 @@
CC = gcc CC = gcc
CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers
LFLAGS = -L../build -lgit2 -lz LFLAGS = -L../build -lgit2 -lz
APPS = general showindex diff rev-list cat-file APPS = general showindex diff rev-list cat-file status log rev-parse init
all: $(APPS) all: $(APPS)

143
examples/add.c Normal file
View File

@ -0,0 +1,143 @@
#include <git2.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
enum print_options {
SKIP = 1,
VERBOSE = 2,
UPDATE = 4,
};
struct print_payload {
enum print_options options;
git_repository *repo;
};
void init_array(git_strarray *array, int argc, char **argv)
{
unsigned int i;
array->count = argc;
array->strings = malloc(sizeof(char*) * array->count);
assert(array->strings!=NULL);
for(i=0; i<array->count; i++) {
array->strings[i]=argv[i];
}
return;
}
int print_matched_cb(const char *path, const char *matched_pathspec, void *payload)
{
struct print_payload p = *(struct print_payload*)(payload);
int ret;
git_status_t status;
(void)matched_pathspec;
if (git_status_file(&status, p.repo, path)) {
return -1; //abort
}
if (status & GIT_STATUS_WT_MODIFIED ||
status & GIT_STATUS_WT_NEW) {
printf("add '%s'\n", path);
ret = 0;
} else {
ret = 1;
}
if(p.options & SKIP) {
ret = 1;
}
return ret;
}
void print_usage(void)
{
fprintf(stderr, "usage: add [options] [--] file-spec [file-spec] [...]\n\n");
fprintf(stderr, "\t-n, --dry-run dry run\n");
fprintf(stderr, "\t-v, --verbose be verbose\n");
fprintf(stderr, "\t-u, --update update tracked files\n");
}
int main (int argc, char** argv)
{
git_index_matched_path_cb matched_cb = NULL;
git_repository *repo = NULL;
git_index *index;
git_strarray array = {0};
int i, options = 0;
struct print_payload payload = {0};
for (i = 1; i < argc; ++i) {
if (argv[i][0] != '-') {
break;
}
else if(!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-v")) {
options |= VERBOSE;
}
else if(!strcmp(argv[i], "--dry-run") || !strcmp(argv[i], "-n")) {
options |= SKIP;
}
else if(!strcmp(argv[i], "--update") || !strcmp(argv[i], "-u")) {
options |= UPDATE;
}
else if(!strcmp(argv[i], "-h")) {
print_usage();
break;
}
else if(!strcmp(argv[i], "--")) {
i++;
break;
}
else {
fprintf(stderr, "Unsupported option %s.\n", argv[i]);
print_usage();
return 1;
}
}
if (argc<=i) {
print_usage();
return 1;
}
git_threads_init();
init_array(&array, argc-i, argv+i);
if (git_repository_open(&repo, ".") < 0) {
fprintf(stderr, "No git repository\n");
return 1;
}
if (git_repository_index(&index, repo) < 0) {
fprintf(stderr, "Could not open repository index\n");
return 1;
}
if (options&VERBOSE || options&SKIP) {
matched_cb = &print_matched_cb;
}
payload.options = options;
payload.repo = repo;
if (options&UPDATE) {
git_index_update_all(index, &array, matched_cb, &payload);
} else {
git_index_add_all(index, &array, 0, matched_cb, &payload);
}
git_index_write(index);
git_index_free(index);
git_repository_free(repo);
git_threads_shutdown();
return 0;
}

245
examples/init.c Normal file
View File

@ -0,0 +1,245 @@
/*
* This is a sample program that is similar to "git init". See the
* documentation for that (try "git help init") to understand what this
* program is emulating.
*
* This demonstrates using the libgit2 APIs to initialize a new repository.
*
* This also contains a special additional option that regular "git init"
* does not support which is "--initial-commit" to make a first empty commit.
* That is demonstrated in the "create_initial_commit" helper function.
*
* 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 <stdio.h>
#include <git2.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* not actually good error handling */
static void fail(const char *msg, const char *arg)
{
if (arg)
fprintf(stderr, "%s %s\n", msg, arg);
else
fprintf(stderr, "%s\n", msg);
exit(1);
}
static void usage(const char *error, const char *arg)
{
fprintf(stderr, "error: %s '%s'\n", error, arg);
fprintf(stderr, "usage: init [-q | --quiet] [--bare] "
"[--template=<dir>] [--shared[=perms]] <directory>\n");
exit(1);
}
/* simple string prefix test used in argument parsing */
static size_t is_prefixed(const char *arg, const char *pfx)
{
size_t len = strlen(pfx);
return !strncmp(arg, pfx, len) ? len : 0;
}
/* parse the tail of the --shared= argument */
static uint32_t parse_shared(const char *shared)
{
if (!strcmp(shared, "false") || !strcmp(shared, "umask"))
return GIT_REPOSITORY_INIT_SHARED_UMASK;
else if (!strcmp(shared, "true") || !strcmp(shared, "group"))
return GIT_REPOSITORY_INIT_SHARED_GROUP;
else if (!strcmp(shared, "all") || !strcmp(shared, "world") ||
!strcmp(shared, "everybody"))
return GIT_REPOSITORY_INIT_SHARED_ALL;
else if (shared[0] == '0') {
long val;
char *end = NULL;
val = strtol(shared + 1, &end, 8);
if (end == shared + 1 || *end != 0)
usage("invalid octal value for --shared", shared);
return (uint32_t)val;
}
else
usage("unknown value for --shared", shared);
return 0;
}
/* forward declaration of helper to make an empty parent-less commit */
static void create_initial_commit(git_repository *repo);
int main(int argc, char *argv[])
{
git_repository *repo = NULL;
int no_options = 1, quiet = 0, bare = 0, initial_commit = 0, i;
uint32_t shared = GIT_REPOSITORY_INIT_SHARED_UMASK;
const char *template = NULL, *gitdir = NULL, *dir = NULL;
size_t pfxlen;
git_threads_init();
/* Process arguments */
for (i = 1; i < argc; ++i) {
char *a = argv[i];
if (a[0] == '-')
no_options = 0;
if (a[0] != '-') {
if (dir != NULL)
usage("extra argument", a);
dir = a;
}
else if (!strcmp(a, "-q") || !strcmp(a, "--quiet"))
quiet = 1;
else if (!strcmp(a, "--bare"))
bare = 1;
else if ((pfxlen = is_prefixed(a, "--template=")) > 0)
template = a + pfxlen;
else if (!strcmp(a, "--separate-git-dir"))
gitdir = argv[++i];
else if ((pfxlen = is_prefixed(a, "--separate-git-dir=")) > 0)
gitdir = a + pfxlen;
else if (!strcmp(a, "--shared"))
shared = GIT_REPOSITORY_INIT_SHARED_GROUP;
else if ((pfxlen = is_prefixed(a, "--shared=")) > 0)
shared = parse_shared(a + pfxlen);
else if (!strcmp(a, "--initial-commit"))
initial_commit = 1;
else
usage("unknown option", a);
}
if (!dir)
usage("must specify directory to init", NULL);
/* Initialize repository */
if (no_options) {
/* No options were specified, so let's demonstrate the default
* simple case of git_repository_init() API usage...
*/
if (git_repository_init(&repo, dir, 0) < 0)
fail("Could not initialize repository", dir);
}
else {
/* Some command line options were specified, so we'll use the
* extended init API to handle them
*/
git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
if (bare)
opts.flags |= GIT_REPOSITORY_INIT_BARE;
if (template) {
opts.flags |= GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
opts.template_path = template;
}
if (gitdir) {
/* if you specified a separate git directory, then initialize
* the repository at that path and use the second path as the
* working directory of the repository (with a git-link file)
*/
opts.workdir_path = dir;
dir = gitdir;
}
if (shared != 0)
opts.mode = shared;
if (git_repository_init_ext(&repo, dir, &opts) < 0)
fail("Could not initialize repository", dir);
}
/* Print a message to stdout like "git init" does */
if (!quiet) {
if (bare || gitdir)
dir = git_repository_path(repo);
else
dir = git_repository_workdir(repo);
printf("Initialized empty Git repository in %s\n", dir);
}
/* As an extension to the basic "git init" command, this example
* gives the option to create an empty initial commit. This is
* mostly to demonstrate what it takes to do that, but also some
* people like to have that empty base commit in their repo.
*/
if (initial_commit) {
create_initial_commit(repo);
printf("Created empty initial commit\n");
}
git_repository_free(repo);
git_threads_shutdown();
return 0;
}
/* Unlike regular "git init", this example shows how to create an initial
* empty commit in the repository. This is the helper function that does
* that.
*/
static void create_initial_commit(git_repository *repo)
{
git_signature *sig;
git_index *index;
git_oid tree_id, commit_id;
git_tree *tree;
/* First use the config to initialize a commit signature for the user */
if (git_signature_default(&sig, repo) < 0)
fail("Unable to create a commit signature.",
"Perhaps 'user.name' and 'user.email' are not set");
/* Now let's create an empty tree for this commit */
if (git_repository_index(&index, repo) < 0)
fail("Could not open repository index", NULL);
/* Outside of this example, you could call git_index_add_bypath()
* here to put actual files into the index. For our purposes, we'll
* leave it empty for now.
*/
if (git_index_write_tree(&tree_id, index) < 0)
fail("Unable to write initial tree from index", NULL);
git_index_free(index);
if (git_tree_lookup(&tree, repo, &tree_id) < 0)
fail("Could not look up initial tree", NULL);
/* Ready to create the initial commit
*
* Normally creating a commit would involve looking up the current
* HEAD commit and making that be the parent of the initial commit,
* but here this is the first commit so there will be no parent.
*/
if (git_commit_create_v(
&commit_id, repo, "HEAD", sig, sig,
NULL, "Initial commit", tree, 0) < 0)
fail("Could not create the initial commit", NULL);
/* Clean up so we don't leak memory */
git_tree_free(tree);
git_signature_free(sig);
}

406
examples/log.c Normal file
View File

@ -0,0 +1,406 @@
#include <stdio.h>
#include <git2.h>
#include <stdlib.h>
#include <string.h>
static void check(int error, const char *message, const char *arg)
{
if (!error)
return;
if (arg)
fprintf(stderr, "%s '%s' (%d)\n", message, arg, error);
else
fprintf(stderr, "%s (%d)\n", message, error);
exit(1);
}
static void usage(const char *message, const char *arg)
{
if (message && arg)
fprintf(stderr, "%s: %s\n", message, arg);
else if (message)
fprintf(stderr, "%s\n", message);
fprintf(stderr, "usage: log [<options>]\n");
exit(1);
}
struct log_state {
git_repository *repo;
const char *repodir;
git_revwalk *walker;
int hide;
int sorting;
};
static void set_sorting(struct log_state *s, unsigned int sort_mode)
{
if (!s->repo) {
if (!s->repodir) s->repodir = ".";
check(git_repository_open_ext(&s->repo, s->repodir, 0, NULL),
"Could not open repository", s->repodir);
}
if (!s->walker)
check(git_revwalk_new(&s->walker, s->repo),
"Could not create revision walker", NULL);
if (sort_mode == GIT_SORT_REVERSE)
s->sorting = s->sorting ^ GIT_SORT_REVERSE;
else
s->sorting = sort_mode | (s->sorting & GIT_SORT_REVERSE);
git_revwalk_sorting(s->walker, s->sorting);
}
static void push_rev(struct log_state *s, git_object *obj, int hide)
{
hide = s->hide ^ hide;
if (!s->walker) {
check(git_revwalk_new(&s->walker, s->repo),
"Could not create revision walker", NULL);
git_revwalk_sorting(s->walker, s->sorting);
}
if (!obj)
check(git_revwalk_push_head(s->walker),
"Could not find repository HEAD", NULL);
else if (hide)
check(git_revwalk_hide(s->walker, git_object_id(obj)),
"Reference does not refer to a commit", NULL);
else
check(git_revwalk_push(s->walker, git_object_id(obj)),
"Reference does not refer to a commit", NULL);
git_object_free(obj);
}
static int add_revision(struct log_state *s, const char *revstr)
{
git_revspec revs;
int hide = 0;
if (!s->repo) {
if (!s->repodir) s->repodir = ".";
check(git_repository_open_ext(&s->repo, s->repodir, 0, NULL),
"Could not open repository", s->repodir);
}
if (!revstr) {
push_rev(s, NULL, hide);
return 0;
}
if (*revstr == '^') {
revs.flags = GIT_REVPARSE_SINGLE;
hide = !hide;
if (git_revparse_single(&revs.from, s->repo, revstr + 1) < 0)
return -1;
} else if (git_revparse(&revs, s->repo, revstr) < 0)
return -1;
if ((revs.flags & GIT_REVPARSE_SINGLE) != 0)
push_rev(s, revs.from, hide);
else {
push_rev(s, revs.to, hide);
if ((revs.flags & GIT_REVPARSE_MERGE_BASE) != 0) {
git_oid base;
check(git_merge_base(&base, s->repo,
git_object_id(revs.from), git_object_id(revs.to)),
"Could not find merge base", revstr);
check(git_object_lookup(&revs.to, s->repo, &base, GIT_OBJ_COMMIT),
"Could not find merge base commit", NULL);
push_rev(s, revs.to, hide);
}
push_rev(s, revs.from, !hide);
}
return 0;
}
static void print_time(const git_time *intime, const char *prefix)
{
char sign, out[32];
struct tm intm;
int offset, hours, minutes;
time_t t;
offset = intime->offset;
if (offset < 0) {
sign = '-';
offset = -offset;
} else {
sign = '+';
}
hours = offset / 60;
minutes = offset % 60;
t = (time_t)intime->time + (intime->offset * 60);
gmtime_r(&t, &intm);
strftime(out, sizeof(out), "%a %b %e %T %Y", &intm);
printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes);
}
static void print_commit(git_commit *commit)
{
char buf[GIT_OID_HEXSZ + 1];
int i, count;
const git_signature *sig;
const char *scan, *eol;
git_oid_tostr(buf, sizeof(buf), git_commit_id(commit));
printf("commit %s\n", buf);
if ((count = (int)git_commit_parentcount(commit)) > 1) {
printf("Merge:");
for (i = 0; i < count; ++i) {
git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
printf(" %s", buf);
}
printf("\n");
}
if ((sig = git_commit_author(commit)) != NULL) {
printf("Author: %s <%s>\n", sig->name, sig->email);
print_time(&sig->when, "Date: ");
}
printf("\n");
for (scan = git_commit_message(commit); scan && *scan; ) {
for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */;
printf(" %.*s\n", (int)(eol - scan), scan);
scan = *eol ? eol + 1 : NULL;
}
printf("\n");
}
static int print_diff(
const git_diff_delta *delta,
const git_diff_range *range,
char usage,
const char *line,
size_t line_len,
void *data)
{
(void)delta; (void)range; (void)usage; (void)line_len; (void)data;
fputs(line, stdout);
return 0;
}
static int match_int(int *value, const char *arg, int allow_negative)
{
char *found;
*value = (int)strtol(arg, &found, 10);
return (found && *found == '\0' && (allow_negative || *value >= 0));
}
static int match_int_arg(
int *value, const char *arg, const char *pfx, int allow_negative)
{
size_t pfxlen = strlen(pfx);
if (strncmp(arg, pfx, pfxlen) != 0)
return 0;
if (!match_int(value, arg + pfxlen, allow_negative))
usage("Invalid value after argument", arg);
return 1;
}
static int match_with_parent(
git_commit *commit, int i, git_diff_options *opts)
{
git_commit *parent;
git_tree *a, *b;
git_diff_list *diff;
int ndeltas;
check(git_commit_parent(&parent, commit, (size_t)i), "Get parent", NULL);
check(git_commit_tree(&a, parent), "Tree for parent", NULL);
check(git_commit_tree(&b, commit), "Tree for commit", NULL);
check(git_diff_tree_to_tree(&diff, git_commit_owner(commit), a, b, opts),
"Checking diff between parent and commit", NULL);
ndeltas = (int)git_diff_num_deltas(diff);
git_diff_list_free(diff);
git_tree_free(a);
git_tree_free(b);
git_commit_free(parent);
return ndeltas > 0;
}
struct log_options {
int show_diff;
int skip, limit;
int min_parents, max_parents;
git_time_t before;
git_time_t after;
char *author;
char *committer;
};
int main(int argc, char *argv[])
{
int i, count = 0, printed = 0, parents;
char *a;
struct log_state s;
struct log_options opt;
git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
git_oid oid;
git_commit *commit = NULL;
git_pathspec *ps = NULL;
git_threads_init();
memset(&s, 0, sizeof(s));
s.sorting = GIT_SORT_TIME;
memset(&opt, 0, sizeof(opt));
opt.max_parents = -1;
opt.limit = -1;
for (i = 1; i < argc; ++i) {
a = argv[i];
if (a[0] != '-') {
if (!add_revision(&s, a))
++count;
else /* try failed revision parse as filename */
break;
} else if (!strcmp(a, "--")) {
++i;
break;
}
else if (!strcmp(a, "--date-order"))
set_sorting(&s, GIT_SORT_TIME);
else if (!strcmp(a, "--topo-order"))
set_sorting(&s, GIT_SORT_TOPOLOGICAL);
else if (!strcmp(a, "--reverse"))
set_sorting(&s, GIT_SORT_REVERSE);
else if (!strncmp(a, "--git-dir=", strlen("--git-dir=")))
s.repodir = a + strlen("--git-dir=");
else if (match_int_arg(&opt.skip, a, "--skip=", 0))
/* found valid --skip */;
else if (match_int_arg(&opt.limit, a, "--max-count=", 0))
/* found valid --max-count */;
else if (a[1] >= '0' && a[1] <= '9') {
if (!match_int(&opt.limit, a + 1, 0))
usage("Invalid limit on number of commits", a);
} else if (!strcmp(a, "-n")) {
if (i + 1 == argc || !match_int(&opt.limit, argv[i + 1], 0))
usage("Argument -n not followed by valid count", argv[i + 1]);
else
++i;
}
else if (!strcmp(a, "--merges"))
opt.min_parents = 2;
else if (!strcmp(a, "--no-merges"))
opt.max_parents = 1;
else if (!strcmp(a, "--no-min-parents"))
opt.min_parents = 0;
else if (!strcmp(a, "--no-max-parents"))
opt.max_parents = -1;
else if (match_int_arg(&opt.max_parents, a, "--max-parents=", 1))
/* found valid --max-parents */;
else if (match_int_arg(&opt.min_parents, a, "--min-parents=", 0))
/* found valid --min_parents */;
else if (!strcmp(a, "-p") || !strcmp(a, "-u") || !strcmp(a, "--patch"))
opt.show_diff = 1;
else
usage("Unsupported argument", a);
}
if (!count)
add_revision(&s, NULL);
diffopts.pathspec.strings = &argv[i];
diffopts.pathspec.count = argc - i;
if (diffopts.pathspec.count > 0)
check(git_pathspec_new(&ps, &diffopts.pathspec),
"Building pathspec", NULL);
printed = count = 0;
for (; !git_revwalk_next(&oid, s.walker); git_commit_free(commit)) {
check(git_commit_lookup(&commit, s.repo, &oid),
"Failed to look up commit", NULL);
parents = (int)git_commit_parentcount(commit);
if (parents < opt.min_parents)
continue;
if (opt.max_parents > 0 && parents > opt.max_parents)
continue;
if (diffopts.pathspec.count > 0) {
int unmatched = parents;
if (parents == 0) {
git_tree *tree;
check(git_commit_tree(&tree, commit), "Get tree", NULL);
if (git_pathspec_match_tree(
NULL, tree, GIT_PATHSPEC_NO_MATCH_ERROR, ps) != 0)
unmatched = 1;
git_tree_free(tree);
} else if (parents == 1) {
unmatched = match_with_parent(commit, 0, &diffopts) ? 0 : 1;
} else {
for (i = 0; i < parents; ++i) {
if (match_with_parent(commit, i, &diffopts))
unmatched--;
}
}
if (unmatched > 0)
continue;
}
if (count++ < opt.skip)
continue;
if (opt.limit != -1 && printed++ >= opt.limit) {
git_commit_free(commit);
break;
}
print_commit(commit);
if (opt.show_diff) {
git_tree *a = NULL, *b = NULL;
git_diff_list *diff = NULL;
if (parents > 1)
continue;
check(git_commit_tree(&b, commit), "Get tree", NULL);
if (parents == 1) {
git_commit *parent;
check(git_commit_parent(&parent, commit, 0), "Get parent", NULL);
check(git_commit_tree(&a, parent), "Tree for parent", NULL);
git_commit_free(parent);
}
check(git_diff_tree_to_tree(
&diff, git_commit_owner(commit), a, b, &diffopts),
"Diff commit with parent", NULL);
check(git_diff_print_patch(diff, print_diff, NULL),
"Displaying diff", NULL);
git_diff_list_free(diff);
git_tree_free(a);
git_tree_free(b);
}
}
git_pathspec_free(ps);
git_revwalk_free(s.walker);
git_repository_free(s.repo);
git_threads_shutdown();
return 0;
}

View File

@ -11,7 +11,8 @@ OBJECTS = \
ls-remote.o \ ls-remote.o \
fetch.o \ fetch.o \
clone.o \ clone.o \
index-pack.o index-pack.o \
common.o
all: $(OBJECTS) all: $(OBJECTS)
$(CC) $(CFLAGS) $(LDFLAGS) -o git2 $(OBJECTS) $(LIBRARIES) $(CC) $(CFLAGS) $(LDFLAGS) -o git2 $(OBJECTS) $(LIBRARIES)

View File

@ -9,19 +9,6 @@
# include <unistd.h> # include <unistd.h>
#endif #endif
/* Shamelessly borrowed from http://stackoverflow.com/questions/3417837/
* with permission of the original author, Martin Pool.
* http://sourcefrog.net/weblog/software/languages/C/unused.html
*/
#ifdef UNUSED
#elif defined(__GNUC__)
# define UNUSED(x) UNUSED_ ## x __attribute__((unused))
#elif defined(__LCLINT__)
# define UNUSED(x) /*@unused@*/ x
#else
# define UNUSED(x) x
#endif
typedef struct progress_data { typedef struct progress_data {
git_transfer_progress fetch_progress; git_transfer_progress fetch_progress;
size_t completed_steps; size_t completed_steps;
@ -63,24 +50,6 @@ static void checkout_progress(const char *path, size_t cur, size_t tot, void *pa
print_progress(pd); print_progress(pd);
} }
static int cred_acquire(git_cred **out,
const char * UNUSED(url),
const char * UNUSED(username_from_url),
unsigned int UNUSED(allowed_types),
void * UNUSED(payload))
{
char username[128] = {0};
char password[128] = {0};
printf("Username: ");
scanf("%s", username);
/* Yup. Right there on your terminal. Careful where you copy/paste output. */
printf("Password: ");
scanf("%s", password);
return git_cred_userpass_plaintext_new(out, username, password);
}
int do_clone(git_repository *repo, int argc, char **argv) int do_clone(git_repository *repo, int argc, char **argv)
{ {
@ -107,7 +76,7 @@ int do_clone(git_repository *repo, int argc, char **argv)
clone_opts.checkout_opts = checkout_opts; clone_opts.checkout_opts = checkout_opts;
clone_opts.fetch_progress_cb = &fetch_progress; clone_opts.fetch_progress_cb = &fetch_progress;
clone_opts.fetch_progress_payload = &pd; clone_opts.fetch_progress_payload = &pd;
clone_opts.cred_acquire_cb = cred_acquire; clone_opts.cred_acquire_cb = cred_acquire_cb;
// Do the clone // Do the clone
error = git_clone(&cloned_repo, url, path, &clone_opts); error = git_clone(&cloned_repo, url, path, &clone_opts);

34
examples/network/common.c Normal file
View File

@ -0,0 +1,34 @@
#include "common.h"
#include <stdio.h>
/* Shamelessly borrowed from http://stackoverflow.com/questions/3417837/
* with permission of the original author, Martin Pool.
* http://sourcefrog.net/weblog/software/languages/C/unused.html
*/
#ifdef UNUSED
#elif defined(__GNUC__)
# define UNUSED(x) UNUSED_ ## x __attribute__((unused))
#elif defined(__LCLINT__)
# define UNUSED(x) /*@unused@*/ x
#else
# define UNUSED(x) x
#endif
int cred_acquire_cb(git_cred **out,
const char * UNUSED(url),
const char * UNUSED(username_from_url),
unsigned int UNUSED(allowed_types),
void * UNUSED(payload))
{
char username[128] = {0};
char password[128] = {0};
printf("Username: ");
scanf("%s", username);
/* Yup. Right there on your terminal. Careful where you copy/paste output. */
printf("Password: ");
scanf("%s", password);
return git_cred_userpass_plaintext_new(out, username, password);
}

View File

@ -12,6 +12,12 @@ int fetch(git_repository *repo, int argc, char **argv);
int index_pack(git_repository *repo, int argc, char **argv); int index_pack(git_repository *repo, int argc, char **argv);
int do_clone(git_repository *repo, int argc, char **argv); int do_clone(git_repository *repo, int argc, char **argv);
int cred_acquire_cb(git_cred **out,
const char * url,
const char * username_from_url,
unsigned int allowed_types,
void *payload);
#ifndef PRIuZ #ifndef PRIuZ
/* Define the printf format specifer to use for size_t output */ /* Define the printf format specifer to use for size_t output */
#if defined(_MSC_VER) || defined(__MINGW32__) #if defined(_MSC_VER) || defined(__MINGW32__)

View File

@ -92,6 +92,7 @@ int fetch(git_repository *repo, int argc, char **argv)
callbacks.update_tips = &update_cb; callbacks.update_tips = &update_cb;
callbacks.progress = &progress_cb; callbacks.progress = &progress_cb;
git_remote_set_callbacks(remote, &callbacks); git_remote_set_callbacks(remote, &callbacks);
git_remote_set_cred_acquire_cb(remote, &cred_acquire_cb, NULL);
// Set up the information for the background worker thread // Set up the information for the background worker thread
data.remote = remote; data.remote = remote;

View File

@ -14,31 +14,6 @@ static int show_ref__cb(git_remote_head *head, void *payload)
return 0; return 0;
} }
static int use_unnamed(git_repository *repo, const char *url)
{
git_remote *remote = NULL;
int error;
// Create an instance of a remote from the URL. The transport to use
// is detected from the URL
error = git_remote_create_inmemory(&remote, repo, NULL, url);
if (error < 0)
goto cleanup;
// When connecting, the underlying code needs to know wether we
// want to push or fetch
error = git_remote_connect(remote, GIT_DIRECTION_FETCH);
if (error < 0)
goto cleanup;
// With git_remote_ls we can retrieve the advertised heads
error = git_remote_ls(remote, &show_ref__cb, NULL);
cleanup:
git_remote_free(remote);
return error;
}
static int use_remote(git_repository *repo, char *name) static int use_remote(git_repository *repo, char *name)
{ {
git_remote *remote = NULL; git_remote *remote = NULL;
@ -46,8 +21,13 @@ static int use_remote(git_repository *repo, char *name)
// Find the remote by name // Find the remote by name
error = git_remote_load(&remote, repo, name); error = git_remote_load(&remote, repo, name);
if (error < 0) {
error = git_remote_create_inmemory(&remote, repo, NULL, name);
if (error < 0) if (error < 0)
goto cleanup; goto cleanup;
}
git_remote_set_cred_acquire_cb(remote, &cred_acquire_cb, NULL);
error = git_remote_connect(remote, GIT_DIRECTION_FETCH); error = git_remote_connect(remote, GIT_DIRECTION_FETCH);
if (error < 0) if (error < 0)
@ -72,12 +52,7 @@ int ls_remote(git_repository *repo, int argc, char **argv)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
/* If there's a ':' in the name, assume it's an URL */
if (strchr(argv[1], ':') != NULL) {
error = use_unnamed(repo, argv[1]);
} else {
error = use_remote(repo, argv[1]); error = use_remote(repo, argv[1]);
}
return error; return error;
} }

106
examples/rev-parse.c Normal file
View File

@ -0,0 +1,106 @@
#include <stdio.h>
#include <git2.h>
#include <stdlib.h>
#include <string.h>
static void check(int error, const char *message, const char *arg)
{
if (!error)
return;
if (arg)
fprintf(stderr, "%s %s (%d)\n", message, arg, error);
else
fprintf(stderr, "%s(%d)\n", message, error);
exit(1);
}
static void usage(const char *message, const char *arg)
{
if (message && arg)
fprintf(stderr, "%s: %s\n", message, arg);
else if (message)
fprintf(stderr, "%s\n", message);
fprintf(stderr, "usage: rev-parse [ --option ] <args>...\n");
exit(1);
}
struct parse_state {
git_repository *repo;
const char *repodir;
int not;
};
static int parse_revision(struct parse_state *ps, const char *revstr)
{
git_revspec rs;
char str[GIT_OID_HEXSZ + 1];
if (!ps->repo) {
if (!ps->repodir)
ps->repodir = ".";
check(git_repository_open_ext(&ps->repo, ps->repodir, 0, NULL),
"Could not open repository from", ps->repodir);
}
check(git_revparse(&rs, ps->repo, revstr), "Could not parse", revstr);
if ((rs.flags & GIT_REVPARSE_SINGLE) != 0) {
git_oid_tostr(str, sizeof(str), git_object_id(rs.from));
printf("%s\n", str);
git_object_free(rs.from);
}
else if ((rs.flags & GIT_REVPARSE_RANGE) != 0) {
git_oid_tostr(str, sizeof(str), git_object_id(rs.to));
printf("%s\n", str);
git_object_free(rs.to);
if ((rs.flags & GIT_REVPARSE_MERGE_BASE) != 0) {
git_oid base;
check(git_merge_base(&base, ps->repo,
git_object_id(rs.from), git_object_id(rs.to)),
"Could not find merge base", revstr);
git_oid_tostr(str, sizeof(str), &base);
printf("%s\n", str);
}
git_oid_tostr(str, sizeof(str), git_object_id(rs.from));
printf("^%s\n", str);
git_object_free(rs.from);
}
else {
check(0, "Invalid results from git_revparse", revstr);
}
return 0;
}
int main(int argc, char *argv[])
{
int i;
char *a;
struct parse_state ps;
git_threads_init();
memset(&ps, 0, sizeof(ps));
for (i = 1; i < argc; ++i) {
a = argv[i];
if (a[0] != '-') {
if (parse_revision(&ps, a) != 0)
break;
} else if (!strcmp(a, "--not"))
ps.not = !ps.not;
else if (!strncmp(a, "--git-dir=", strlen("--git-dir=")))
ps.repodir = a + strlen("--git-dir=");
else
usage("Cannot handle argument", a);
}
git_repository_free(ps.repo);
git_threads_shutdown();
return 0;
}

443
examples/status.c Normal file
View File

@ -0,0 +1,443 @@
/*
* 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.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum {
FORMAT_DEFAULT = 0,
FORMAT_LONG = 1,
FORMAT_SHORT = 2,
FORMAT_PORCELAIN = 3,
};
#define MAX_PATHSPEC 8
/*
* This example demonstrates the use of the libgit2 status APIs,
* particularly the `git_status_list` object, to roughly simulate the
* output of running `git status`. It serves as a simple example of
* using those APIs to get basic status information.
*
* This does not have:
* - Robust error handling
* - Colorized or paginated output formatting
*
* This does have:
* - Examples of translating command line arguments to the status
* options settings to mimic `git status` results.
* - A sample status formatter that matches the default "long" format
* from `git status`
* - A sample status formatter that matches the "short" format
*/
static void check(int error, const char *message, const char *extra)
{
const git_error *lg2err;
const char *lg2msg = "", *lg2spacer = "";
if (!error)
return;
if ((lg2err = giterr_last()) != NULL && lg2err->message != NULL) {
lg2msg = lg2err->message;
lg2spacer = " - ";
}
if (extra)
fprintf(stderr, "%s '%s' [%d]%s%s\n",
message, extra, error, lg2spacer, lg2msg);
else
fprintf(stderr, "%s [%d]%s%s\n",
message, error, lg2spacer, lg2msg);
exit(1);
}
static void fail(const char *message)
{
check(-1, message, NULL);
}
static void show_branch(git_repository *repo, int format)
{
int error = 0;
const char *branch = NULL;
git_reference *head = NULL;
error = git_repository_head(&head, repo);
if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND)
branch = NULL;
else if (!error) {
branch = git_reference_name(head);
if (!strncmp(branch, "refs/heads/", strlen("refs/heads/")))
branch += strlen("refs/heads/");
} else
check(error, "failed to get current branch", NULL);
if (format == FORMAT_LONG)
printf("# On branch %s\n",
branch ? branch : "Not currently on any branch.");
else
printf("## %s\n", branch ? branch : "HEAD (no branch)");
git_reference_free(head);
}
static void print_long(git_repository *repo, git_status_list *status)
{
size_t i, maxi = git_status_list_entrycount(status);
const git_status_entry *s;
int header = 0, changes_in_index = 0;
int changed_in_workdir = 0, rm_in_workdir = 0;
const char *old_path, *new_path;
(void)repo;
/* print index changes */
for (i = 0; i < maxi; ++i) {
char *istatus = NULL;
s = git_status_byindex(status, i);
if (s->status == GIT_STATUS_CURRENT)
continue;
if (s->status & GIT_STATUS_WT_DELETED)
rm_in_workdir = 1;
if (s->status & GIT_STATUS_INDEX_NEW)
istatus = "new file: ";
if (s->status & GIT_STATUS_INDEX_MODIFIED)
istatus = "modified: ";
if (s->status & GIT_STATUS_INDEX_DELETED)
istatus = "deleted: ";
if (s->status & GIT_STATUS_INDEX_RENAMED)
istatus = "renamed: ";
if (s->status & GIT_STATUS_INDEX_TYPECHANGE)
istatus = "typechange:";
if (istatus == NULL)
continue;
if (!header) {
printf("# Changes to be committed:\n");
printf("# (use \"git reset HEAD <file>...\" to unstage)\n");
printf("#\n");
header = 1;
}
old_path = s->head_to_index->old_file.path;
new_path = s->head_to_index->new_file.path;
if (old_path && new_path && strcmp(old_path, new_path))
printf("#\t%s %s -> %s\n", istatus, old_path, new_path);
else
printf("#\t%s %s\n", istatus, old_path ? old_path : new_path);
}
if (header) {
changes_in_index = 1;
printf("#\n");
}
header = 0;
/* print workdir changes to tracked files */
for (i = 0; i < maxi; ++i) {
char *wstatus = NULL;
s = git_status_byindex(status, i);
if (s->status == GIT_STATUS_CURRENT || s->index_to_workdir == NULL)
continue;
if (s->status & GIT_STATUS_WT_MODIFIED)
wstatus = "modified: ";
if (s->status & GIT_STATUS_WT_DELETED)
wstatus = "deleted: ";
if (s->status & GIT_STATUS_WT_RENAMED)
wstatus = "renamed: ";
if (s->status & GIT_STATUS_WT_TYPECHANGE)
wstatus = "typechange:";
if (wstatus == NULL)
continue;
if (!header) {
printf("# Changes not staged for commit:\n");
printf("# (use \"git add%s <file>...\" to update what will be committed)\n", rm_in_workdir ? "/rm" : "");
printf("# (use \"git checkout -- <file>...\" to discard changes in working directory)\n");
printf("#\n");
header = 1;
}
old_path = s->index_to_workdir->old_file.path;
new_path = s->index_to_workdir->new_file.path;
if (old_path && new_path && strcmp(old_path, new_path))
printf("#\t%s %s -> %s\n", wstatus, old_path, new_path);
else
printf("#\t%s %s\n", wstatus, old_path ? old_path : new_path);
}
if (header) {
changed_in_workdir = 1;
printf("#\n");
}
header = 0;
/* print untracked files */
header = 0;
for (i = 0; i < maxi; ++i) {
s = git_status_byindex(status, i);
if (s->status == GIT_STATUS_WT_NEW) {
if (!header) {
printf("# Untracked files:\n");
printf("# (use \"git add <file>...\" to include in what will be committed)\n");
printf("#\n");
header = 1;
}
printf("#\t%s\n", s->index_to_workdir->old_file.path);
}
}
header = 0;
/* print ignored files */
for (i = 0; i < maxi; ++i) {
s = git_status_byindex(status, i);
if (s->status == GIT_STATUS_IGNORED) {
if (!header) {
printf("# Ignored files:\n");
printf("# (use \"git add -f <file>...\" to include in what will be committed)\n");
printf("#\n");
header = 1;
}
printf("#\t%s\n", s->index_to_workdir->old_file.path);
}
}
if (!changes_in_index && changed_in_workdir)
printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
}
static void print_short(git_repository *repo, git_status_list *status)
{
size_t i, maxi = git_status_list_entrycount(status);
const git_status_entry *s;
char istatus, wstatus;
const char *extra, *a, *b, *c;
for (i = 0; i < maxi; ++i) {
s = git_status_byindex(status, i);
if (s->status == GIT_STATUS_CURRENT)
continue;
a = b = c = NULL;
istatus = wstatus = ' ';
extra = "";
if (s->status & GIT_STATUS_INDEX_NEW)
istatus = 'A';
if (s->status & GIT_STATUS_INDEX_MODIFIED)
istatus = 'M';
if (s->status & GIT_STATUS_INDEX_DELETED)
istatus = 'D';
if (s->status & GIT_STATUS_INDEX_RENAMED)
istatus = 'R';
if (s->status & GIT_STATUS_INDEX_TYPECHANGE)
istatus = 'T';
if (s->status & GIT_STATUS_WT_NEW) {
if (istatus == ' ')
istatus = '?';
wstatus = '?';
}
if (s->status & GIT_STATUS_WT_MODIFIED)
wstatus = 'M';
if (s->status & GIT_STATUS_WT_DELETED)
wstatus = 'D';
if (s->status & GIT_STATUS_WT_RENAMED)
wstatus = 'R';
if (s->status & GIT_STATUS_WT_TYPECHANGE)
wstatus = 'T';
if (s->status & GIT_STATUS_IGNORED) {
istatus = '!';
wstatus = '!';
}
if (istatus == '?' && wstatus == '?')
continue;
if (s->index_to_workdir &&
s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT)
{
git_submodule *sm = NULL;
unsigned int smstatus = 0;
if (!git_submodule_lookup(
&sm, repo, s->index_to_workdir->new_file.path) &&
!git_submodule_status(&smstatus, sm))
{
if (smstatus & GIT_SUBMODULE_STATUS_WD_MODIFIED)
extra = " (new commits)";
else if (smstatus & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED)
extra = " (modified content)";
else if (smstatus & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED)
extra = " (modified content)";
else if (smstatus & GIT_SUBMODULE_STATUS_WD_UNTRACKED)
extra = " (untracked content)";
}
}
if (s->head_to_index) {
a = s->head_to_index->old_file.path;
b = s->head_to_index->new_file.path;
}
if (s->index_to_workdir) {
if (!a)
a = s->index_to_workdir->old_file.path;
if (!b)
b = s->index_to_workdir->old_file.path;
c = s->index_to_workdir->new_file.path;
}
if (istatus == 'R') {
if (wstatus == 'R')
printf("%c%c %s %s %s%s\n", istatus, wstatus, a, b, c, extra);
else
printf("%c%c %s %s%s\n", istatus, wstatus, a, b, extra);
} else {
if (wstatus == 'R')
printf("%c%c %s %s%s\n", istatus, wstatus, a, c, extra);
else
printf("%c%c %s%s\n", istatus, wstatus, a, extra);
}
}
for (i = 0; i < maxi; ++i) {
s = git_status_byindex(status, i);
if (s->status == GIT_STATUS_WT_NEW)
printf("?? %s\n", s->index_to_workdir->old_file.path);
}
}
int main(int argc, char *argv[])
{
git_repository *repo = NULL;
int i, npaths = 0, format = FORMAT_DEFAULT, zterm = 0, showbranch = 0;
git_status_options opt = GIT_STATUS_OPTIONS_INIT;
git_status_list *status;
char *repodir = ".", *pathspec[MAX_PATHSPEC];
opt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
opt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
for (i = 1; i < argc; ++i) {
if (argv[i][0] != '-') {
if (npaths < MAX_PATHSPEC)
pathspec[npaths++] = argv[i];
else
fail("Example only supports a limited pathspec");
}
else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--short"))
format = FORMAT_SHORT;
else if (!strcmp(argv[i], "--long"))
format = FORMAT_LONG;
else if (!strcmp(argv[i], "--porcelain"))
format = FORMAT_PORCELAIN;
else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--branch"))
showbranch = 1;
else if (!strcmp(argv[i], "-z")) {
zterm = 1;
if (format == FORMAT_DEFAULT)
format = FORMAT_PORCELAIN;
}
else if (!strcmp(argv[i], "--ignored"))
opt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
else if (!strcmp(argv[i], "-uno") ||
!strcmp(argv[i], "--untracked-files=no"))
opt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED;
else if (!strcmp(argv[i], "-unormal") ||
!strcmp(argv[i], "--untracked-files=normal"))
opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
else if (!strcmp(argv[i], "-uall") ||
!strcmp(argv[i], "--untracked-files=all"))
opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
else if (!strcmp(argv[i], "--ignore-submodules=all"))
opt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
else if (!strncmp(argv[i], "--git-dir=", strlen("--git-dir=")))
repodir = argv[i] + strlen("--git-dir=");
else
check(-1, "Unsupported option", argv[i]);
}
if (format == FORMAT_DEFAULT)
format = FORMAT_LONG;
if (format == FORMAT_LONG)
showbranch = 1;
if (npaths > 0) {
opt.pathspec.strings = pathspec;
opt.pathspec.count = npaths;
}
/*
* Try to open the repository at the given path (or at the current
* directory if none was given).
*/
check(git_repository_open_ext(&repo, repodir, 0, NULL),
"Could not open repository", repodir);
if (git_repository_is_bare(repo))
fail("Cannot report status on bare repository");
/*
* Run status on the repository
*
* Because we want to simluate a full "git status" run and want to
* support some command line options, we use `git_status_foreach_ext()`
* instead of just the plain status call. This allows (a) iterating
* over the index and then the workdir and (b) extra flags that control
* which files are included. If you just want simple status (e.g. to
* enumerate files that are modified) then you probably don't need the
* extended API.
*/
check(git_status_list_new(&status, repo, &opt),
"Could not get status", NULL);
if (showbranch)
show_branch(repo, format);
if (format == FORMAT_LONG)
print_long(repo, status);
else
print_short(repo, status);
git_status_list_free(status);
git_repository_free(repo);
return 0;
}

View File

@ -56,6 +56,7 @@
#include "git2/message.h" #include "git2/message.h"
#include "git2/pack.h" #include "git2/pack.h"
#include "git2/stash.h" #include "git2/stash.h"
#include "git2/pathspec.h"
#include "git2/blame.h" #include "git2/blame.h"
#endif #endif

View File

@ -162,7 +162,7 @@ GIT_EXTERN(int) git_attr_get(
* Then you could loop through the 3 values to get the settings for * Then you could loop through the 3 values to get the settings for
* the three attributes you asked about. * the three attributes you asked about.
* *
* @param values An array of num_attr entries that will have string * @param values_out An array of num_attr entries that will have string
* pointers written into it for the values of the attributes. * pointers written into it for the values of the attributes.
* You should not modify or free the values that are written * You should not modify or free the values that are written
* into this array (although of course, you should free the * into this array (although of course, you should free the

View File

@ -183,6 +183,8 @@ typedef enum {
GIT_CHECKOUT_NOTIFY_UPDATED = (1u << 2), GIT_CHECKOUT_NOTIFY_UPDATED = (1u << 2),
GIT_CHECKOUT_NOTIFY_UNTRACKED = (1u << 3), GIT_CHECKOUT_NOTIFY_UNTRACKED = (1u << 3),
GIT_CHECKOUT_NOTIFY_IGNORED = (1u << 4), GIT_CHECKOUT_NOTIFY_IGNORED = (1u << 4),
GIT_CHECKOUT_NOTIFY_ALL = 0x0FFFFu
} git_checkout_notify_t; } git_checkout_notify_t;
/** Checkout notification callback function */ /** Checkout notification callback function */
@ -234,6 +236,8 @@ typedef struct git_checkout_opts {
git_strarray paths; git_strarray paths;
git_tree *baseline; /** expected content of workdir, defaults to HEAD */ git_tree *baseline; /** expected content of workdir, defaults to HEAD */
const char *target_directory; /** alternative checkout path to workdir */
} git_checkout_opts; } git_checkout_opts;
#define GIT_CHECKOUT_OPTS_VERSION 1 #define GIT_CHECKOUT_OPTS_VERSION 1

View File

@ -67,6 +67,7 @@ typedef struct git_clone_options {
unsigned int version; unsigned int version;
git_checkout_opts checkout_opts; git_checkout_opts checkout_opts;
git_repository_init_options *init_options;
int bare; int bare;
git_transfer_progress_callback fetch_progress_cb; git_transfer_progress_callback fetch_progress_cb;
void *fetch_progress_payload; void *fetch_progress_payload;

View File

@ -24,17 +24,24 @@ GIT_BEGIN_DECL
/** /**
* Lookup a commit object from a repository. * Lookup a commit object from a repository.
* *
* The returned object should be released with `git_commit_free` when no
* longer needed.
*
* @param commit pointer to the looked up commit * @param commit pointer to the looked up commit
* @param repo the repo to use when locating the commit. * @param repo the repo to use when locating the commit.
* @param id identity of the commit to locate. If the object is * @param id identity of the commit to locate. If the object is
* an annotated tag it will be peeled back to the commit. * an annotated tag it will be peeled back to the commit.
* @return 0 or an error code * @return 0 or an error code
*/ */
GIT_EXTERN(int) git_commit_lookup(git_commit **commit, git_repository *repo, const git_oid *id); GIT_EXTERN(int) git_commit_lookup(
git_commit **commit, git_repository *repo, const git_oid *id);
/** /**
* Lookup a commit object from a repository, * Lookup a commit object from a repository, given a prefix of its
* given a prefix of its identifier (short id). * identifier (short id).
*
* The returned object should be released with `git_commit_free` when no
* longer needed.
* *
* @see git_object_lookup_prefix * @see git_object_lookup_prefix
* *
@ -45,7 +52,8 @@ GIT_EXTERN(int) git_commit_lookup(git_commit **commit, git_repository *repo, con
* @param len the length of the short identifier * @param len the length of the short identifier
* @return 0 or an error code * @return 0 or an error code
*/ */
GIT_EXTERN(int) git_commit_lookup_prefix(git_commit **commit, git_repository *repo, const git_oid *id, size_t len); GIT_EXTERN(int) git_commit_lookup_prefix(
git_commit **commit, git_repository *repo, const git_oid *id, size_t len);
/** /**
* Close an open commit * Close an open commit
@ -129,6 +137,14 @@ GIT_EXTERN(const git_signature *) git_commit_committer(const git_commit *commit)
*/ */
GIT_EXTERN(const git_signature *) git_commit_author(const git_commit *commit); GIT_EXTERN(const git_signature *) git_commit_author(const git_commit *commit);
/**
* Get the full raw text of the commit header.
*
* @param commit a previously loaded commit
* @return the header text of the commit
*/
GIT_EXTERN(const char *) git_commit_raw_header(const git_commit *commit);
/** /**
* Get the tree pointed to by a commit. * Get the tree pointed to by a commit.
* *
@ -235,7 +251,7 @@ GIT_EXTERN(int) git_commit_nth_gen_ancestor(
* *
* @param parent_count Number of parents for this commit * @param parent_count Number of parents for this commit
* *
* @param parents[] Array of `parent_count` pointers to `git_commit` * @param parents Array of `parent_count` pointers to `git_commit`
* objects that will be used as the parents for this commit. This * objects that will be used as the parents for this commit. This
* array may be NULL if `parent_count` is 0 (root commit). All the * array may be NULL if `parent_count` is 0 (root commit). All the
* given commits must be owned by the `repo`. * given commits must be owned by the `repo`.

View File

@ -105,7 +105,8 @@ GIT_EXTERN(void) git_libgit2_version(int *major, int *minor, int *rev);
*/ */
typedef enum { typedef enum {
GIT_CAP_THREADS = ( 1 << 0 ), GIT_CAP_THREADS = ( 1 << 0 ),
GIT_CAP_HTTPS = ( 1 << 1 ) GIT_CAP_HTTPS = ( 1 << 1 ),
GIT_CAP_SSH = ( 1 << 2 ),
} git_cap_t; } git_cap_t;
/** /**

View File

@ -61,6 +61,7 @@ typedef struct {
} git_config_entry; } git_config_entry;
typedef int (*git_config_foreach_cb)(const git_config_entry *, void *); typedef int (*git_config_foreach_cb)(const git_config_entry *, void *);
typedef struct git_config_iterator git_config_iterator;
typedef enum { typedef enum {
GIT_CVAR_FALSE = 0, GIT_CVAR_FALSE = 0,
@ -119,7 +120,7 @@ GIT_EXTERN(int) git_config_find_xdg(char *out, size_t length);
* If /etc/gitconfig doesn't exist, it will look for * If /etc/gitconfig doesn't exist, it will look for
* %PROGRAMFILES%\Git\etc\gitconfig. * %PROGRAMFILES%\Git\etc\gitconfig.
* @param global_config_path Buffer to store the path in * @param out Buffer to store the path in
* @param length size of the buffer in bytes * @param length size of the buffer in bytes
* @return 0 if a system configuration file has been * @return 0 if a system configuration file has been
* found. Its path will be stored in `buffer`. * found. Its path will be stored in `buffer`.
@ -327,7 +328,7 @@ GIT_EXTERN(int) git_config_get_bool(int *out, const git_config *cfg, const char
GIT_EXTERN(int) git_config_get_string(const char **out, const git_config *cfg, const char *name); GIT_EXTERN(int) git_config_get_string(const char **out, const git_config *cfg, const char *name);
/** /**
* Get each value of a multivar. * Get each value of a multivar in a foreach callback
* *
* The callback will be called on each variable found * The callback will be called on each variable found
* *
@ -335,10 +336,37 @@ GIT_EXTERN(int) git_config_get_string(const char **out, const git_config *cfg, c
* @param name the variable's name * @param name the variable's name
* @param regexp regular expression to filter which variables we're * @param regexp regular expression to filter which variables we're
* interested in. Use NULL to indicate all * interested in. Use NULL to indicate all
* @param fn the function to be called on each value of the variable * @param callback the function to be called on each value of the variable
* @param data opaque pointer to pass to the callback * @param payload opaque pointer to pass to the callback
*/ */
GIT_EXTERN(int) git_config_get_multivar(const git_config *cfg, const char *name, const char *regexp, git_config_foreach_cb callback, void *payload); GIT_EXTERN(int) git_config_get_multivar_foreach(const git_config *cfg, const char *name, const char *regexp, git_config_foreach_cb callback, void *payload);
/**
* Get each value of a multivar
*
* @param out pointer to store the iterator
* @param cfg where to look for the variable
* @param name the variable's name
* @param regexp regular expression to filter which variables we're
* interested in. Use NULL to indicate all
*/
GIT_EXTERN(int) git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp);
/**
* Return the current entry and advance the iterator
*
* @param entry pointer to store the entry
* @param iter the iterator
* @return 0 or an error code. GIT_ITEROVER if the iteration has completed
*/
GIT_EXTERN(int) git_config_next(git_config_entry **entry, git_config_iterator *iter);
/**
* Free a config iterator
*
* @param iter the iterator to free
*/
GIT_EXTERN(void) git_config_iterator_free(git_config_iterator *iter);
/** /**
* Set the value of an integer config variable in the config file * Set the value of an integer config variable in the config file
@ -424,6 +452,29 @@ GIT_EXTERN(int) git_config_foreach(
git_config_foreach_cb callback, git_config_foreach_cb callback,
void *payload); void *payload);
/**
* Iterate over all the config variables
*
* Use `git_config_next` to advance the iteration and
* `git_config_iterator_free` when done.
*
* @param out pointer to store the iterator
* @param cfg where to ge the variables from
*/
GIT_EXTERN(int) git_config_iterator_new(git_config_iterator **out, const git_config *cfg);
/**
* Iterate over all the config variables whose name matches a pattern
*
* Use `git_config_next` to advance the iteration and
* `git_config_iterator_free` when done.
*
* @param out pointer to store the iterator
* @param cfg where to ge the variables from
* @param regexp regular expression to match the names
*/
GIT_EXTERN(int) git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp);
/** /**
* Perform an operation on each config variable matching a regular expression. * Perform an operation on each config variable matching a regular expression.
* *
@ -535,6 +586,25 @@ GIT_EXTERN(int) git_config_parse_int32(int32_t *out, const char *value);
GIT_EXTERN(int) git_config_parse_int64(int64_t *out, const char *value); GIT_EXTERN(int) git_config_parse_int64(int64_t *out, const char *value);
/**
* Perform an operation on each config variable in given config backend
* matching a regular expression.
*
* This behaviors like `git_config_foreach_match` except instead of all config
* entries it just enumerates through the given backend entry.
*
* @param backend where to get the variables from
* @param regexp regular expression to match against config names (can be NULL)
* @param callback the function to call on each variable
* @param payload the data to pass to the callback
*/
GIT_EXTERN(int) git_config_backend_foreach_match(
git_config_backend *backend,
const char *regexp,
int (*fn)(const git_config_entry *, void *),
void *data);
/** @} */ /** @} */
GIT_END_DECL GIT_END_DECL
#endif #endif

View File

@ -7,7 +7,7 @@
#ifndef INCLUDE_git_cred_helpers_h__ #ifndef INCLUDE_git_cred_helpers_h__
#define INCLUDE_git_cred_helpers_h__ #define INCLUDE_git_cred_helpers_h__
#include "git2/transport.h" #include "transport.h"
/** /**
* @file git2/cred_helpers.h * @file git2/cred_helpers.h

View File

@ -78,7 +78,7 @@ typedef enum {
GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1 << 3), GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1 << 3),
/** Ignore whitespace at end of line */ /** Ignore whitespace at end of line */
GIT_DIFF_IGNORE_WHITESPACE_EOL = (1 << 4), GIT_DIFF_IGNORE_WHITESPACE_EOL = (1 << 4),
/** Exclude submodules from the diff completely */ /** Treat all submodules as unmodified */
GIT_DIFF_IGNORE_SUBMODULES = (1 << 5), GIT_DIFF_IGNORE_SUBMODULES = (1 << 5),
/** Use the "patience diff" algorithm (currently unimplemented) */ /** Use the "patience diff" algorithm (currently unimplemented) */
GIT_DIFF_PATIENCE = (1 << 6), GIT_DIFF_PATIENCE = (1 << 6),
@ -314,6 +314,8 @@ typedef int (*git_diff_notify_cb)(
* - `notify_cb` is an optional callback function, notifying the consumer of * - `notify_cb` is an optional callback function, notifying the consumer of
* which files are being examined as the diff is generated * which files are being examined as the diff is generated
* - `notify_payload` is the payload data to pass to the `notify_cb` function * - `notify_payload` is the payload data to pass to the `notify_cb` function
* - `ignore_submodules` overrides the submodule ignore setting for all
* submodules in the diff.
*/ */
typedef struct { typedef struct {
unsigned int version; /**< version for the struct */ unsigned int version; /**< version for the struct */
@ -326,6 +328,7 @@ typedef struct {
git_off_t max_size; /**< defaults to 512MB */ git_off_t max_size; /**< defaults to 512MB */
git_diff_notify_cb notify_cb; git_diff_notify_cb notify_cb;
void *notify_payload; void *notify_payload;
git_submodule_ignore_t ignore_submodules; /** << submodule ignore rule */
} git_diff_options; } git_diff_options;
#define GIT_DIFF_OPTIONS_VERSION 1 #define GIT_DIFF_OPTIONS_VERSION 1
@ -388,7 +391,7 @@ typedef enum {
*/ */
GIT_DIFF_LINE_FILE_HDR = 'F', GIT_DIFF_LINE_FILE_HDR = 'F',
GIT_DIFF_LINE_HUNK_HDR = 'H', GIT_DIFF_LINE_HUNK_HDR = 'H',
GIT_DIFF_LINE_BINARY = 'B' GIT_DIFF_LINE_BINARY = 'B' /**< For "Binary files x and y differ" */
} git_diff_line_t; } git_diff_line_t;
/** /**
@ -451,6 +454,9 @@ typedef enum {
GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE = (1 << 13), GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE = (1 << 13),
/** measure similarity only by comparing SHAs (fast and cheap) */ /** measure similarity only by comparing SHAs (fast and cheap) */
GIT_DIFF_FIND_EXACT_MATCH_ONLY = (1 << 14), GIT_DIFF_FIND_EXACT_MATCH_ONLY = (1 << 14),
/** do not break rewrites unless they contribute to a rename */
GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY = (1 << 15),
} git_diff_find_t; } git_diff_find_t;
/** /**
@ -747,7 +753,7 @@ GIT_EXTERN(int) git_diff_print_raw(
* letters for your own purposes. This function does just that. By the * letters for your own purposes. This function does just that. By the
* way, unmodified will return a space (i.e. ' '). * way, unmodified will return a space (i.e. ' ').
* *
* @param delta_t The git_delta_t value to look up * @param status The git_delta_t value to look up
* @return The single character label for that code * @return The single character label for that code
*/ */
GIT_EXTERN(char) git_diff_status_char(git_delta_t status); GIT_EXTERN(char) git_diff_status_char(git_delta_t status);
@ -797,6 +803,14 @@ GIT_EXTERN(size_t) git_diff_num_deltas_of_type(
git_diff_list *diff, git_diff_list *diff,
git_delta_t type); git_delta_t type);
/**
* Check if deltas are sorted case sensitively or insensitively.
*
* @param diff Diff list to check
* @return 0 if case sensitive, 1 if case is ignored
*/
GIT_EXTERN(int) git_diff_is_sorted_icase(const git_diff_list *diff);
/** /**
* Return the diff delta and patch for an entry in the diff list. * Return the diff delta and patch for an entry in the diff list.
* *
@ -918,7 +932,7 @@ GIT_EXTERN(int) git_diff_patch_num_lines_in_hunk(
* @param new_lineno Line number in new file or -1 if line is deleted * @param new_lineno Line number in new file or -1 if line is deleted
* @param patch The patch to look in * @param patch The patch to look in
* @param hunk_idx The index of the hunk * @param hunk_idx The index of the hunk
* @param line_of_index The index of the line in the hunk * @param line_of_hunk The index of the line in the hunk
* @return 0 on success, <0 on failure * @return 0 on success, <0 on failure
*/ */
GIT_EXTERN(int) git_diff_patch_get_line_in_hunk( GIT_EXTERN(int) git_diff_patch_get_line_in_hunk(
@ -931,6 +945,28 @@ GIT_EXTERN(int) git_diff_patch_get_line_in_hunk(
size_t hunk_idx, size_t hunk_idx,
size_t line_of_hunk); size_t line_of_hunk);
/**
* Look up size of patch diff data in bytes
*
* This returns the raw size of the patch data. This only includes the
* actual data from the lines of the diff, not the file or hunk headers.
*
* If you pass `include_context` as true (non-zero), this will be the size
* of all of the diff output; if you pass it as false (zero), this will
* only include the actual changed lines (as if `context_lines` was 0).
*
* @param patch A git_diff_patch representing changes to one file
* @param include_context Include context lines in size if non-zero
* @param include_hunk_headers Include hunk header lines if non-zero
* @param include_file_headers Include file header lines if non-zero
* @return The number of bytes of data
*/
GIT_EXTERN(size_t) git_diff_patch_size(
git_diff_patch *patch,
int include_context,
int include_hunk_headers,
int include_file_headers);
/** /**
* Serialize the patch to text via callback. * Serialize the patch to text via callback.
* *
@ -983,7 +1019,9 @@ GIT_EXTERN(int) git_diff_patch_to_str(
* `GIT_DIFF_FORCE_TEXT` of course). * `GIT_DIFF_FORCE_TEXT` of course).
* *
* @param old_blob Blob for old side of diff, or NULL for empty blob * @param old_blob Blob for old side of diff, or NULL for empty blob
* @param old_as_path Treat old blob as if it had this filename; can be NULL
* @param new_blob Blob for new side of diff, or NULL for empty blob * @param new_blob Blob for new side of diff, or NULL for empty blob
* @param new_as_path Treat new blob as if it had this filename; can be NULL
* @param options Options for diff, or NULL for default options * @param options Options for diff, or NULL for default options
* @param file_cb Callback for "file"; made once if there is a diff; can be NULL * @param file_cb Callback for "file"; made once if there is a diff; can be NULL
* @param hunk_cb Callback for each hunk in diff; can be NULL * @param hunk_cb Callback for each hunk in diff; can be NULL
@ -993,7 +1031,9 @@ GIT_EXTERN(int) git_diff_patch_to_str(
*/ */
GIT_EXTERN(int) git_diff_blobs( GIT_EXTERN(int) git_diff_blobs(
const git_blob *old_blob, const git_blob *old_blob,
const char *old_as_path,
const git_blob *new_blob, const git_blob *new_blob,
const char *new_as_path,
const git_diff_options *options, const git_diff_options *options,
git_diff_file_cb file_cb, git_diff_file_cb file_cb,
git_diff_hunk_cb hunk_cb, git_diff_hunk_cb hunk_cb,
@ -1010,14 +1050,18 @@ GIT_EXTERN(int) git_diff_blobs(
* *
* @param out The generated patch; NULL on error * @param out The generated patch; NULL on error
* @param old_blob Blob for old side of diff, or NULL for empty blob * @param old_blob Blob for old side of diff, or NULL for empty blob
* @param old_as_path Treat old blob as if it had this filename; can be NULL
* @param new_blob Blob for new side of diff, or NULL for empty blob * @param new_blob Blob for new side of diff, or NULL for empty blob
* @param options Options for diff, or NULL for default options * @param new_as_path Treat new blob as if it had this filename; can be NULL
* @param opts Options for diff, or NULL for default options
* @return 0 on success or error code < 0 * @return 0 on success or error code < 0
*/ */
GIT_EXTERN(int) git_diff_patch_from_blobs( GIT_EXTERN(int) git_diff_patch_from_blobs(
git_diff_patch **out, git_diff_patch **out,
const git_blob *old_blob, const git_blob *old_blob,
const char *old_as_path,
const git_blob *new_blob, const git_blob *new_blob,
const char *new_as_path,
const git_diff_options *opts); const git_diff_options *opts);
/** /**
@ -1033,19 +1077,23 @@ GIT_EXTERN(int) git_diff_patch_from_blobs(
* the reverse, with GIT_DELTA_REMOVED and blob content removed. * the reverse, with GIT_DELTA_REMOVED and blob content removed.
* *
* @param old_blob Blob for old side of diff, or NULL for empty blob * @param old_blob Blob for old side of diff, or NULL for empty blob
* @param old_as_path Treat old blob as if it had this filename; can be NULL
* @param buffer Raw data for new side of diff, or NULL for empty * @param buffer Raw data for new side of diff, or NULL for empty
* @param buffer_len Length of raw data for new side of diff * @param buffer_len Length of raw data for new side of diff
* @param buffer_as_path Treat buffer as if it had this filename; can be NULL
* @param options Options for diff, or NULL for default options * @param options Options for diff, or NULL for default options
* @param file_cb Callback for "file"; made once if there is a diff; can be NULL * @param file_cb Callback for "file"; made once if there is a diff; can be NULL
* @param hunk_cb Callback for each hunk in diff; can be NULL * @param hunk_cb Callback for each hunk in diff; can be NULL
* @param line_cb Callback for each line in diff; can be NULL * @param data_cb Callback for each line in diff; can be NULL
* @param payload Payload passed to each callback function * @param payload Payload passed to each callback function
* @return 0 on success, GIT_EUSER on non-zero callback return, or error code * @return 0 on success, GIT_EUSER on non-zero callback return, or error code
*/ */
GIT_EXTERN(int) git_diff_blob_to_buffer( GIT_EXTERN(int) git_diff_blob_to_buffer(
const git_blob *old_blob, const git_blob *old_blob,
const char *old_as_path,
const char *buffer, const char *buffer,
size_t buffer_len, size_t buffer_len,
const char *buffer_as_path,
const git_diff_options *options, const git_diff_options *options,
git_diff_file_cb file_cb, git_diff_file_cb file_cb,
git_diff_hunk_cb hunk_cb, git_diff_hunk_cb hunk_cb,
@ -1062,16 +1110,20 @@ GIT_EXTERN(int) git_diff_blob_to_buffer(
* *
* @param out The generated patch; NULL on error * @param out The generated patch; NULL on error
* @param old_blob Blob for old side of diff, or NULL for empty blob * @param old_blob Blob for old side of diff, or NULL for empty blob
* @param old_as_path Treat old blob as if it had this filename; can be NULL
* @param buffer Raw data for new side of diff, or NULL for empty * @param buffer Raw data for new side of diff, or NULL for empty
* @param buffer_len Length of raw data for new side of diff * @param buffer_len Length of raw data for new side of diff
* @param options Options for diff, or NULL for default options * @param buffer_as_path Treat buffer as if it had this filename; can be NULL
* @param opts Options for diff, or NULL for default options
* @return 0 on success or error code < 0 * @return 0 on success or error code < 0
*/ */
GIT_EXTERN(int) git_diff_patch_from_blob_and_buffer( GIT_EXTERN(int) git_diff_patch_from_blob_and_buffer(
git_diff_patch **out, git_diff_patch **out,
const git_blob *old_blob, const git_blob *old_blob,
const char *buf, const char *old_as_path,
size_t buflen, const char *buffer,
size_t buffer_len,
const char *buffer_as_path,
const git_diff_options *opts); const git_diff_options *opts);

View File

@ -32,6 +32,7 @@ typedef enum {
GIT_ENONFASTFORWARD = -11, GIT_ENONFASTFORWARD = -11,
GIT_EINVALIDSPEC = -12, GIT_EINVALIDSPEC = -12,
GIT_EMERGECONFLICT = -13, GIT_EMERGECONFLICT = -13,
GIT_ELOCKED = -14,
GIT_PASSTHROUGH = -30, GIT_PASSTHROUGH = -30,
GIT_ITEROVER = -31, GIT_ITEROVER = -31,
@ -100,7 +101,7 @@ GIT_EXTERN(void) giterr_clear(void);
* *
* @param error_class One of the `git_error_t` enum above describing the * @param error_class One of the `git_error_t` enum above describing the
* general subsystem that is responsible for the error. * general subsystem that is responsible for the error.
* @param message The formatted error message to keep * @param string The formatted error message to keep
*/ */
GIT_EXTERN(void) giterr_set_str(int error_class, const char *string); GIT_EXTERN(void) giterr_set_str(int error_class, const char *string);

View File

@ -11,6 +11,7 @@
#include "indexer.h" #include "indexer.h"
#include "types.h" #include "types.h"
#include "oid.h" #include "oid.h"
#include "strarray.h"
/** /**
* @file git2/index.h * @file git2/index.h
@ -125,6 +126,26 @@ typedef enum {
GIT_INDEXCAP_FROM_OWNER = ~0u GIT_INDEXCAP_FROM_OWNER = ~0u
} git_indexcap_t; } git_indexcap_t;
/** Callback for APIs that add/remove/update files matching pathspec */
typedef int (*git_index_matched_path_cb)(
const char *path, const char *matched_pathspec, void *payload);
/** Flags for APIs that add files matching pathspec */
typedef enum {
GIT_INDEX_ADD_DEFAULT = 0,
GIT_INDEX_ADD_FORCE = (1u << 0),
GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH = (1u << 1),
GIT_INDEX_ADD_CHECK_PATHSPEC = (1u << 2),
} git_index_add_option_t;
/**
* Match any index stage.
*
* Some index APIs take a stage to match; pass this value to match
* any entry matching the path regardless of stage.
*/
#define GIT_INDEX_STAGE_ANY -1
/** @name Index File Functions /** @name Index File Functions
* *
* These functions work on the index file itself. * These functions work on the index file itself.
@ -218,6 +239,14 @@ GIT_EXTERN(int) git_index_read(git_index *index);
*/ */
GIT_EXTERN(int) git_index_write(git_index *index); GIT_EXTERN(int) git_index_write(git_index *index);
/**
* Get the full path to the index file on disk.
*
* @param index an existing index object
* @return path to index file or NULL for in-memory index
*/
GIT_EXTERN(const char *) git_index_path(git_index *index);
/** /**
* Read a tree into the index file with stats * Read a tree into the index file with stats
* *
@ -420,6 +449,108 @@ GIT_EXTERN(int) git_index_add_bypath(git_index *index, const char *path);
*/ */
GIT_EXTERN(int) git_index_remove_bypath(git_index *index, const char *path); GIT_EXTERN(int) git_index_remove_bypath(git_index *index, const char *path);
/**
* Add or update index entries matching files in the working directory.
*
* This method will fail in bare index instances.
*
* The `pathspec` is a list of file names or shell glob patterns that will
* matched against files in the repository's working directory. Each file
* that matches will be added to the index (either updating an existing
* entry or adding a new entry). You can disable glob expansion and force
* exact matching with the `GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH` flag.
*
* Files that are ignored will be skipped (unlike `git_index_add_bypath`).
* If a file is already tracked in the index, then it *will* be updated
* even if it is ignored. Pass the `GIT_INDEX_ADD_FORCE` flag to
* skip the checking of ignore rules.
*
* To emulate `git add -A` and generate an error if the pathspec contains
* the exact path of an ignored file (when not using FORCE), add the
* `GIT_INDEX_ADD_CHECK_PATHSPEC` flag. This checks that each entry
* in the `pathspec` that is an exact match to a filename on disk is
* either not ignored or already in the index. If this check fails, the
* function will return GIT_EINVALIDSPEC.
*
* To emulate `git add -A` with the "dry-run" option, just use a callback
* function that always returns a positive value. See below for details.
*
* If any files are currently the result of a merge conflict, those files
* will no longer be marked as conflicting. The data about the conflicts
* will be moved to the "resolve undo" (REUC) section.
*
* If you provide a callback function, it will be invoked on each matching
* item in the working directory immediately *before* it is added to /
* updated in the index. Returning zero will add the item to the index,
* greater than zero will skip the item, and less than zero will abort the
* scan and cause GIT_EUSER to be returned.
*
* @param index an existing index object
* @param pathspec array of path patterns
* @param flags combination of git_index_add_option_t flags
* @param callback notification callback for each added/updated path (also
* gets index of matching pathspec entry); can be NULL;
* return 0 to add, >0 to skip, <0 to abort scan.
* @param payload payload passed through to callback function
* @return 0 or an error code
*/
GIT_EXTERN(int) git_index_add_all(
git_index *index,
const git_strarray *pathspec,
unsigned int flags,
git_index_matched_path_cb callback,
void *payload);
/**
* Remove all matching index entries.
*
* If you provide a callback function, it will be invoked on each matching
* item in the index immediately *before* it is removed. Return 0 to
* remove the item, > 0 to skip the item, and < 0 to abort the scan.
*
* @param index An existing index object
* @param pathspec array of path patterns
* @param callback notification callback for each removed path (also
* gets index of matching pathspec entry); can be NULL;
* return 0 to add, >0 to skip, <0 to abort scan.
* @param payload payload passed through to callback function
* @return 0 or an error code
*/
GIT_EXTERN(int) git_index_remove_all(
git_index *index,
const git_strarray *pathspec,
git_index_matched_path_cb callback,
void *payload);
/**
* Update all index entries to match the working directory
*
* This method will fail in bare index instances.
*
* This scans the existing index entries and synchronizes them with the
* working directory, deleting them if the corresponding working directory
* file no longer exists otherwise updating the information (including
* adding the latest version of file to the ODB if needed).
*
* If you provide a callback function, it will be invoked on each matching
* item in the index immediately *before* it is updated (either refreshed
* or removed depending on working directory state). Return 0 to proceed
* with updating the item, > 0 to skip the item, and < 0 to abort the scan.
*
* @param index An existing index object
* @param pathspec array of path patterns
* @param callback notification callback for each updated path (also
* gets index of matching pathspec entry); can be NULL;
* return 0 to add, >0 to skip, <0 to abort scan.
* @param payload payload passed through to callback function
* @return 0 or an error code
*/
GIT_EXTERN(int) git_index_update_all(
git_index *index,
const git_strarray *pathspec,
git_index_matched_path_cb callback,
void *payload);
/** /**
* Find the first position of any entries which point to given * Find the first position of any entries which point to given
* path in the Git index. * path in the Git index.
@ -531,7 +662,7 @@ GIT_EXTERN(int) git_index_conflict_next(
/** /**
* Frees a `git_index_conflict_iterator`. * Frees a `git_index_conflict_iterator`.
* *
* @param it pointer to the iterator * @param iterator pointer to the iterator
*/ */
GIT_EXTERN(void) git_index_conflict_iterator_free( GIT_EXTERN(void) git_index_conflict_iterator_free(
git_index_conflict_iterator *iterator); git_index_conflict_iterator *iterator);

View File

@ -21,7 +21,7 @@ typedef struct git_indexer_stream git_indexer_stream;
* @param out where to store the indexer instance * @param out where to store the indexer instance
* @param path to the directory where the packfile should be stored * @param path to the directory where the packfile should be stored
* @param progress_cb function to call with progress information * @param progress_cb function to call with progress information
* @param progress_payload payload for the progress callback * @param progress_cb_payload payload for the progress callback
*/ */
GIT_EXTERN(int) git_indexer_stream_new( GIT_EXTERN(int) git_indexer_stream_new(
git_indexer_stream **out, git_indexer_stream **out,

View File

@ -7,11 +7,11 @@
#ifndef INCLUDE_git_merge_h__ #ifndef INCLUDE_git_merge_h__
#define INCLUDE_git_merge_h__ #define INCLUDE_git_merge_h__
#include "git2/common.h" #include "common.h"
#include "git2/types.h" #include "types.h"
#include "git2/oid.h" #include "oid.h"
#include "git2/checkout.h" #include "checkout.h"
#include "git2/index.h" #include "index.h"
/** /**
* @file git2/merge.h * @file git2/merge.h
@ -141,7 +141,7 @@ GIT_EXTERN(int) git_merge_head_from_oid(
/** /**
* Frees a `git_merge_head` * Frees a `git_merge_head`
* *
* @param the merge head to free * @param head merge head to free
*/ */
GIT_EXTERN(void) git_merge_head_free( GIT_EXTERN(void) git_merge_head_free(
git_merge_head *head); git_merge_head *head);

View File

@ -99,7 +99,7 @@ GIT_EXTERN(int) git_note_read(
/** /**
* Get the note message * Get the note message
* *
* @param note * @param note the note
* @return the note message * @return the note message
*/ */
GIT_EXTERN(const char *) git_note_message(const git_note *note); GIT_EXTERN(const char *) git_note_message(const git_note *note);
@ -108,7 +108,7 @@ GIT_EXTERN(const char *) git_note_message(const git_note *note);
/** /**
* Get the note object OID * Get the note object OID
* *
* @param note * @param note the note
* @return the note object OID * @return the note object OID
*/ */
GIT_EXTERN(const git_oid *) git_note_oid(const git_note *note); GIT_EXTERN(const git_oid *) git_note_oid(const git_note *note);

View File

@ -36,7 +36,7 @@ GIT_BEGIN_DECL
* @param repo the repository to look up the object * @param repo the repository to look up the object
* @param id the unique identifier for the object * @param id the unique identifier for the object
* @param type the type of the object * @param type the type of the object
* @return a reference to the object * @return 0 or an error code
*/ */
GIT_EXTERN(int) git_object_lookup( GIT_EXTERN(int) git_object_lookup(
git_object **object, git_object **object,

View File

@ -219,18 +219,12 @@ GIT_EXTERN(int) git_odb_write(git_oid *out, git_odb *odb, const void *data, size
* The type and final length of the object must be specified * The type and final length of the object must be specified
* when opening the stream. * when opening the stream.
* *
* The returned stream will be of type `GIT_STREAM_WRONLY` and * The returned stream will be of type `GIT_STREAM_WRONLY`, and it
* will have the following methods: * won't be effective until `git_odb_stream_finalize_write` is called
* and returns without an error
* *
* - stream->write: write `n` bytes into the stream * The stream must always be freed when done with `git_odb_stream_free` or
* - stream->finalize_write: close the stream and store the object in * will leak memory.
* the odb
* - stream->free: free the stream
*
* The streaming write won't be effective until `stream->finalize_write`
* is called and returns without an error
*
* The stream must always be free'd or will leak memory.
* *
* @see git_odb_stream * @see git_odb_stream
* *
@ -242,6 +236,48 @@ GIT_EXTERN(int) git_odb_write(git_oid *out, git_odb *odb, const void *data, size
*/ */
GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **out, git_odb *db, size_t size, git_otype type); GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **out, git_odb *db, size_t size, git_otype type);
/**
* Write to an odb stream
*
* This method will fail if the total number of received bytes exceeds the
* size declared with `git_odb_open_wstream()`
*
* @param stream the stream
* @param buffer the data to write
* @param len the buffer's length
* @return 0 if the write succeeded; error code otherwise
*/
GIT_EXTERN(int) git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len);
/**
* Finish writing to an odb stream
*
* The object will take its final name and will be available to the
* odb.
*
* This method will fail if the total number of received bytes
* differs from the size declared with `git_odb_open_wstream()`
*
* @param out pointer to store the resulting object's id
* @param stream the stream
* @return 0 on success; an error code otherwise
*/
GIT_EXTERN(int) git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream);
/**
* Read from an odb stream
*
* Most backends don't implement streaming reads
*/
GIT_EXTERN(int) git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len);
/**
* Free an odb stream
*
* @param stream the stream to free
*/
GIT_EXTERN(void) git_odb_stream_free(git_odb_stream *stream);
/** /**
* Open a stream to read an object from the ODB * Open a stream to read an object from the ODB
* *

View File

@ -7,8 +7,8 @@
#ifndef INCLUDE_git_odb_backend_h__ #ifndef INCLUDE_git_odb_backend_h__
#define INCLUDE_git_odb_backend_h__ #define INCLUDE_git_odb_backend_h__
#include "git2/common.h" #include "common.h"
#include "git2/types.h" #include "types.h"
/** /**
* @file git2/backend.h * @file git2/backend.h
@ -65,14 +65,50 @@ typedef enum {
GIT_STREAM_RW = (GIT_STREAM_RDONLY | GIT_STREAM_WRONLY), GIT_STREAM_RW = (GIT_STREAM_RDONLY | GIT_STREAM_WRONLY),
} git_odb_stream_t; } git_odb_stream_t;
/** A stream to read/write from a backend */ /**
* A stream to read/write from a backend.
*
* This represents a stream of data being written to or read from a
* backend. When writing, the frontend functions take care of
* calculating the object's id and all `finalize_write` needs to do is
* store the object with the id it is passed.
*/
struct git_odb_stream { struct git_odb_stream {
git_odb_backend *backend; git_odb_backend *backend;
unsigned int mode; unsigned int mode;
void *hash_ctx;
size_t declared_size;
size_t received_bytes;
/**
* Write at most `len` bytes into `buffer` and advance the stream.
*/
int (*read)(git_odb_stream *stream, char *buffer, size_t len); int (*read)(git_odb_stream *stream, char *buffer, size_t len);
/**
* Write `len` bytes from `buffer` into the stream.
*/
int (*write)(git_odb_stream *stream, const char *buffer, size_t len); int (*write)(git_odb_stream *stream, const char *buffer, size_t len);
int (*finalize_write)(git_oid *oid_p, git_odb_stream *stream);
/**
* Store the contents of the stream as an object with the id
* specified in `oid`.
*
* This method might not be invoked if:
* - an error occurs earlier with the `write` callback,
* - the object referred to by `oid` already exists in any backend, or
* - the final number of received bytes differs from the size declared
* with `git_odb_open_wstream()`
*/
int (*finalize_write)(git_odb_stream *stream, const git_oid *oid);
/**
* Free the stream's memory.
*
* This method might be called without a call to `finalize_write` if
* an error occurs or if the object is already present in the ODB.
*/
void (*free)(git_odb_stream *stream); void (*free)(git_odb_stream *stream);
}; };

View File

@ -85,7 +85,7 @@ GIT_EXTERN(void) git_oid_fromraw(git_oid *out, const unsigned char *raw);
* needed for an oid encoded in hex (40 bytes). Only the * needed for an oid encoded in hex (40 bytes). Only the
* oid digits are written; a '\\0' terminator must be added * oid digits are written; a '\\0' terminator must be added
* by the caller if it is required. * by the caller if it is required.
* @param oid oid structure to format. * @param id oid structure to format.
*/ */
GIT_EXTERN(void) git_oid_fmt(char *out, const git_oid *id); GIT_EXTERN(void) git_oid_fmt(char *out, const git_oid *id);
@ -96,7 +96,7 @@ GIT_EXTERN(void) git_oid_fmt(char *out, const git_oid *id);
* If the number of bytes is > GIT_OID_HEXSZ, extra bytes * If the number of bytes is > GIT_OID_HEXSZ, extra bytes
* will be zeroed; if not, a '\0' terminator is NOT added. * will be zeroed; if not, a '\0' terminator is NOT added.
* @param n number of characters to write into out string * @param n number of characters to write into out string
* @param oid oid structure to format. * @param id oid structure to format.
*/ */
GIT_EXTERN(void) git_oid_nfmt(char *out, size_t n, const git_oid *id); GIT_EXTERN(void) git_oid_nfmt(char *out, size_t n, const git_oid *id);
@ -118,7 +118,7 @@ GIT_EXTERN(void) git_oid_pathfmt(char *out, const git_oid *id);
/** /**
* Format a git_oid into a newly allocated c-string. * Format a git_oid into a newly allocated c-string.
* *
* @param oid the oid structure to format * @param id the oid structure to format
* @return the c-string; NULL if memory is exhausted. Caller must * @return the c-string; NULL if memory is exhausted. Caller must
* deallocate the string with git__free(). * deallocate the string with git__free().
*/ */
@ -188,8 +188,7 @@ GIT_EXTERN(int) git_oid_ncmp(const git_oid *a, const git_oid *b, size_t len);
* *
* @param id oid structure. * @param id oid structure.
* @param str input hex string of an object id. * @param str input hex string of an object id.
* @return GIT_ENOTOID if str is not a valid hex string, * @return 0 in case of a match, -1 otherwise.
* 0 in case of a match, GIT_ERROR otherwise.
*/ */
GIT_EXTERN(int) git_oid_streq(const git_oid *id, const char *str); GIT_EXTERN(int) git_oid_streq(const git_oid *id, const char *str);
@ -241,13 +240,13 @@ GIT_EXTERN(git_oid_shorten *) git_oid_shorten_new(size_t min_length);
* or freed. * or freed.
* *
* For performance reasons, there is a hard-limit of how many * For performance reasons, there is a hard-limit of how many
* OIDs can be added to a single set (around ~22000, assuming * OIDs can be added to a single set (around ~32000, assuming
* a mostly randomized distribution), which should be enough * a mostly randomized distribution), which should be enough
* for any kind of program, and keeps the algorithm fast and * for any kind of program, and keeps the algorithm fast and
* memory-efficient. * memory-efficient.
* *
* Attempting to add more than those OIDs will result in a * Attempting to add more than those OIDs will result in a
* GIT_ENOMEM error * GITERR_INVALID error
* *
* @param os a `git_oid_shorten` instance * @param os a `git_oid_shorten` instance
* @param text_id an OID in text form * @param text_id an OID in text form

View File

@ -112,7 +112,7 @@ GIT_EXTERN(int) git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid
* @param pb The packbuilder * @param pb The packbuilder
* @param path to the directory where the packfile and index should be stored * @param path to the directory where the packfile and index should be stored
* @param progress_cb function to call with progress information from the indexer (optional) * @param progress_cb function to call with progress information from the indexer (optional)
* @param progress_payload payload for the progress callback (optional) * @param progress_cb_payload payload for the progress callback (optional)
* *
* @return 0 or an error code * @return 0 or an error code
*/ */
@ -137,7 +137,7 @@ GIT_EXTERN(int) git_packbuilder_foreach(git_packbuilder *pb, git_packbuilder_for
* Get the total number of objects the packbuilder will write out * Get the total number of objects the packbuilder will write out
* *
* @param pb the packbuilder * @param pb the packbuilder
* @return * @return the number of objects in the packfile
*/ */
GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb); GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb);
@ -145,7 +145,7 @@ GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb);
* Get the number of objects the packbuilder has already written out * Get the number of objects the packbuilder has already written out
* *
* @param pb the packbuilder * @param pb the packbuilder
* @return * @return the number of objects which have already been written
*/ */
GIT_EXTERN(uint32_t) git_packbuilder_written(git_packbuilder *pb); GIT_EXTERN(uint32_t) git_packbuilder_written(git_packbuilder *pb);

260
include/git2/pathspec.h Normal file
View File

@ -0,0 +1,260 @@
/*
* 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_pathspec_h__
#define INCLUDE_git_pathspec_h__
#include "common.h"
#include "types.h"
#include "strarray.h"
#include "diff.h"
/**
* Compiled pathspec
*/
typedef struct git_pathspec git_pathspec;
/**
* List of filenames matching a pathspec
*/
typedef struct git_pathspec_match_list git_pathspec_match_list;
/**
* Options controlling how pathspec match should be executed
*
* - GIT_PATHSPEC_IGNORE_CASE forces match to ignore case; otherwise
* match will use native case sensitivity of platform filesystem
* - GIT_PATHSPEC_USE_CASE forces case sensitive match; otherwise
* match will use native case sensitivity of platform filesystem
* - GIT_PATHSPEC_NO_GLOB disables glob patterns and just uses simple
* string comparison for matching
* - GIT_PATHSPEC_NO_MATCH_ERROR means the match functions return error
* code GIT_ENOTFOUND if no matches are found; otherwise no matches is
* still success (return 0) but `git_pathspec_match_list_entrycount`
* will indicate 0 matches.
* - GIT_PATHSPEC_FIND_FAILURES means that the `git_pathspec_match_list`
* should track which patterns matched which files so that at the end of
* the match we can identify patterns that did not match any files.
* - GIT_PATHSPEC_FAILURES_ONLY means that the `git_pathspec_match_list`
* does not need to keep the actual matching filenames. Use this to
* just test if there were any matches at all or in combination with
* GIT_PATHSPEC_FIND_FAILURES to validate a pathspec.
*/
typedef enum {
GIT_PATHSPEC_DEFAULT = 0,
GIT_PATHSPEC_IGNORE_CASE = (1u << 0),
GIT_PATHSPEC_USE_CASE = (1u << 1),
GIT_PATHSPEC_NO_GLOB = (1u << 2),
GIT_PATHSPEC_NO_MATCH_ERROR = (1u << 3),
GIT_PATHSPEC_FIND_FAILURES = (1u << 4),
GIT_PATHSPEC_FAILURES_ONLY = (1u << 5),
} git_pathspec_flag_t;
/**
* Compile a pathspec
*
* @param out Output of the compiled pathspec
* @param pathspec A git_strarray of the paths to match
* @return 0 on success, <0 on failure
*/
GIT_EXTERN(int) git_pathspec_new(
git_pathspec **out, const git_strarray *pathspec);
/**
* Free a pathspec
*
* @param ps The compiled pathspec
*/
GIT_EXTERN(void) git_pathspec_free(git_pathspec *ps);
/**
* Try to match a path against a pathspec
*
* Unlike most of the other pathspec matching functions, this will not
* fall back on the native case-sensitivity for your platform. You must
* explicitly pass flags to control case sensitivity or else this will
* fall back on being case sensitive.
*
* @param ps The compiled pathspec
* @param flags Combination of git_pathspec_flag_t options to control match
* @param path The pathname to attempt to match
* @return 1 is path matches spec, 0 if it does not
*/
GIT_EXTERN(int) git_pathspec_matches_path(
const git_pathspec *ps, uint32_t flags, const char *path);
/**
* Match a pathspec against the working directory of a repository.
*
* This matches the pathspec against the current files in the working
* directory of the repository. It is an error to invoke this on a bare
* repo. This handles git ignores (i.e. ignored files will not be
* considered to match the `pathspec` unless the file is tracked in the
* index).
*
* If `out` is not NULL, this returns a `git_patchspec_match_list`. That
* contains the list of all matched filenames (unless you pass the
* `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
* pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
* flag). You must call `git_pathspec_match_list_free()` on this object.
*
* @param out Output list of matches; pass NULL to just get return value
* @param repo The repository in which to match; bare repo is an error
* @param flags Combination of git_pathspec_flag_t options to control match
* @param ps Pathspec to be matched
* @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
* the GIT_PATHSPEC_NO_MATCH_ERROR flag was given
*/
GIT_EXTERN(int) git_pathspec_match_workdir(
git_pathspec_match_list **out,
git_repository *repo,
uint32_t flags,
git_pathspec *ps);
/**
* Match a pathspec against entries in an index.
*
* This matches the pathspec against the files in the repository index.
*
* NOTE: At the moment, the case sensitivity of this match is controlled
* by the current case-sensitivity of the index object itself and the
* USE_CASE and IGNORE_CASE flags will have no effect. This behavior will
* be corrected in a future release.
*
* If `out` is not NULL, this returns a `git_patchspec_match_list`. That
* contains the list of all matched filenames (unless you pass the
* `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
* pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
* flag). You must call `git_pathspec_match_list_free()` on this object.
*
* @param out Output list of matches; pass NULL to just get return value
* @param index The index to match against
* @param flags Combination of git_pathspec_flag_t options to control match
* @param ps Pathspec to be matched
* @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
* the GIT_PATHSPEC_NO_MATCH_ERROR flag is used
*/
GIT_EXTERN(int) git_pathspec_match_index(
git_pathspec_match_list **out,
git_index *index,
uint32_t flags,
git_pathspec *ps);
/**
* Match a pathspec against files in a tree.
*
* This matches the pathspec against the files in the given tree.
*
* If `out` is not NULL, this returns a `git_patchspec_match_list`. That
* contains the list of all matched filenames (unless you pass the
* `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
* pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
* flag). You must call `git_pathspec_match_list_free()` on this object.
*
* @param out Output list of matches; pass NULL to just get return value
* @param tree The root-level tree to match against
* @param flags Combination of git_pathspec_flag_t options to control match
* @param ps Pathspec to be matched
* @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
* the GIT_PATHSPEC_NO_MATCH_ERROR flag is used
*/
GIT_EXTERN(int) git_pathspec_match_tree(
git_pathspec_match_list **out,
git_tree *tree,
uint32_t flags,
git_pathspec *ps);
/**
* Match a pathspec against files in a diff list.
*
* This matches the pathspec against the files in the given diff list.
*
* If `out` is not NULL, this returns a `git_patchspec_match_list`. That
* contains the list of all matched filenames (unless you pass the
* `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
* pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
* flag). You must call `git_pathspec_match_list_free()` on this object.
*
* @param out Output list of matches; pass NULL to just get return value
* @param diff A generated diff list
* @param flags Combination of git_pathspec_flag_t options to control match
* @param ps Pathspec to be matched
* @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
* the GIT_PATHSPEC_NO_MATCH_ERROR flag is used
*/
GIT_EXTERN(int) git_pathspec_match_diff(
git_pathspec_match_list **out,
git_diff_list *diff,
uint32_t flags,
git_pathspec *ps);
/**
* Free memory associates with a git_pathspec_match_list
*
* @param m The git_pathspec_match_list to be freed
*/
GIT_EXTERN(void) git_pathspec_match_list_free(git_pathspec_match_list *m);
/**
* Get the number of items in a match list.
*
* @param m The git_pathspec_match_list object
* @return Number of items in match list
*/
GIT_EXTERN(size_t) git_pathspec_match_list_entrycount(
const git_pathspec_match_list *m);
/**
* Get a matching filename by position.
*
* This routine cannot be used if the match list was generated by
* `git_pathspec_match_diff`. If so, it will always return NULL.
*
* @param m The git_pathspec_match_list object
* @param pos The index into the list
* @return The filename of the match
*/
GIT_EXTERN(const char *) git_pathspec_match_list_entry(
const git_pathspec_match_list *m, size_t pos);
/**
* Get a matching diff delta by position.
*
* This routine can only be used if the match list was generated by
* `git_pathspec_match_diff`. Otherwise it will always return NULL.
*
* @param m The git_pathspec_match_list object
* @param pos The index into the list
* @return The filename of the match
*/
GIT_EXTERN(const git_diff_delta *) git_pathspec_match_list_diff_entry(
const git_pathspec_match_list *m, size_t pos);
/**
* Get the number of pathspec items that did not match.
*
* This will be zero unless you passed GIT_PATHSPEC_FIND_FAILURES when
* generating the git_pathspec_match_list.
*
* @param m The git_pathspec_match_list object
* @return Number of items in original pathspec that had no matches
*/
GIT_EXTERN(size_t) git_pathspec_match_list_failed_entrycount(
const git_pathspec_match_list *m);
/**
* Get an original pathspec string that had no matches.
*
* This will be return NULL for positions out of range.
*
* @param m The git_pathspec_match_list object
* @param pos The index into the failed items
* @return The pathspec pattern that didn't match anything
*/
GIT_EXTERN(const char *) git_pathspec_match_list_failed_entry(
const git_pathspec_match_list *m, size_t pos);
#endif

View File

@ -98,7 +98,7 @@ GIT_EXTERN(int) git_push_finish(git_push *push);
* *
* @param push The push object * @param push The push object
* *
* @return true if equal, false otherwise * @return true if remote side successfully unpacked, false otherwise
*/ */
GIT_EXTERN(int) git_push_unpack_ok(git_push *push); GIT_EXTERN(int) git_push_unpack_ok(git_push *push);

View File

@ -32,7 +32,7 @@ GIT_BEGIN_DECL
* @param out pointer to the looked-up reference * @param out pointer to the looked-up reference
* @param repo the repository to look up the reference * @param repo the repository to look up the reference
* @param name the long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...) * @param name the long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...)
* @return 0 on success, ENOTFOUND, EINVALIDSPEC or an error code. * @return 0 on success, GIT_ENOTFOUND, GIT_EINVALIDSPEC or an error code.
*/ */
GIT_EXTERN(int) git_reference_lookup(git_reference **out, git_repository *repo, const char *name); GIT_EXTERN(int) git_reference_lookup(git_reference **out, git_repository *repo, const char *name);
@ -49,7 +49,7 @@ GIT_EXTERN(int) git_reference_lookup(git_reference **out, git_repository *repo,
* @param out Pointer to oid to be filled in * @param out Pointer to oid to be filled in
* @param repo The repository in which to look up the reference * @param repo The repository in which to look up the reference
* @param name The long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...) * @param name The long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...)
* @return 0 on success, ENOTFOUND, EINVALIDSPEC or an error code. * @return 0 on success, GIT_ENOTFOUND, GIT_EINVALIDSPEC or an error code.
*/ */
GIT_EXTERN(int) git_reference_name_to_id( GIT_EXTERN(int) git_reference_name_to_id(
git_oid *out, git_repository *repo, const char *name); git_oid *out, git_repository *repo, const char *name);
@ -62,7 +62,7 @@ GIT_EXTERN(int) git_reference_name_to_id(
* *
* @param out pointer in which to store the reference * @param out pointer in which to store the reference
* @param repo the repository in which to look * @param repo the repository in which to look
* @param shrothand the short name for the reference * @param shorthand the short name for the reference
* @return 0 or an error code * @return 0 or an error code
*/ */
GIT_EXTERN(int) git_reference_dwim(git_reference **out, git_repository *repo, const char *shorthand); GIT_EXTERN(int) git_reference_dwim(git_reference **out, git_repository *repo, const char *shorthand);
@ -94,7 +94,7 @@ GIT_EXTERN(int) git_reference_dwim(git_reference **out, git_repository *repo, co
* @param name The name of the reference * @param name The name of the reference
* @param target The target of the reference * @param target The target of the reference
* @param force Overwrite existing references * @param force Overwrite existing references
* @return 0 on success, EEXISTS, EINVALIDSPEC or an error code * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code
*/ */
GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repository *repo, const char *name, const char *target, int force); GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repository *repo, const char *name, const char *target, int force);
@ -126,7 +126,7 @@ GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repositor
* @param name The name of the reference * @param name The name of the reference
* @param id The object id pointed to by the reference. * @param id The object id pointed to by the reference.
* @param force Overwrite existing references * @param force Overwrite existing references
* @return 0 on success, EEXISTS, EINVALIDSPEC or an error code * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code
*/ */
GIT_EXTERN(int) git_reference_create(git_reference **out, git_repository *repo, const char *name, const git_oid *id, int force); GIT_EXTERN(int) git_reference_create(git_reference **out, git_repository *repo, const char *name, const git_oid *id, int force);
@ -198,7 +198,7 @@ GIT_EXTERN(const char *) git_reference_name(const git_reference *ref);
* If a direct reference is passed as an argument, a copy of that * If a direct reference is passed as an argument, a copy of that
* reference is returned. This copy must be manually freed too. * reference is returned. This copy must be manually freed too.
* *
* @param resolved_ref Pointer to the peeled reference * @param out Pointer to the peeled reference
* @param ref The reference * @param ref The reference
* @return 0 or an error code * @return 0 or an error code
*/ */
@ -225,7 +225,7 @@ GIT_EXTERN(git_repository *) git_reference_owner(const git_reference *ref);
* @param out Pointer to the newly created reference * @param out Pointer to the newly created reference
* @param ref The reference * @param ref The reference
* @param target The new target for the reference * @param target The new target for the reference
* @return 0 on success, EINVALIDSPEC or an error code * @return 0 on success, GIT_EINVALIDSPEC or an error code
*/ */
GIT_EXTERN(int) git_reference_symbolic_set_target( GIT_EXTERN(int) git_reference_symbolic_set_target(
git_reference **out, git_reference **out,
@ -266,9 +266,9 @@ GIT_EXTERN(int) git_reference_set_target(
* the reflog if it exists. * the reflog if it exists.
* *
* @param ref The reference to rename * @param ref The reference to rename
* @param name The new name for the reference * @param new_name The new name for the reference
* @param force Overwrite an existing reference * @param force Overwrite an existing reference
* @return 0 on success, EINVALIDSPEC, EEXISTS or an error code * @return 0 on success, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code
* *
*/ */
GIT_EXTERN(int) git_reference_rename( GIT_EXTERN(int) git_reference_rename(
@ -375,7 +375,7 @@ GIT_EXTERN(int) git_reference_iterator_glob_new(
* *
* @param out pointer in which to store the reference * @param out pointer in which to store the reference
* @param iter the iterator * @param iter the iterator
* @param 0, GIT_ITEROVER if there are no more; or an error code * @return 0, GIT_ITEROVER if there are no more; or an error code
*/ */
GIT_EXTERN(int) git_reference_next(git_reference **out, git_reference_iterator *iter); GIT_EXTERN(int) git_reference_next(git_reference **out, git_reference_iterator *iter);
@ -442,6 +442,15 @@ GIT_EXTERN(int) git_reference_is_branch(git_reference *ref);
*/ */
GIT_EXTERN(int) git_reference_is_remote(git_reference *ref); GIT_EXTERN(int) git_reference_is_remote(git_reference *ref);
/**
* Check if a reference is a tag
*
* @param ref A git reference
*
* @return 1 when the reference lives in the refs/tags
* namespace; 0 otherwise.
*/
GIT_EXTERN(int) git_reference_is_tag(git_reference *ref);
typedef enum { typedef enum {
GIT_REF_FORMAT_NORMAL = 0, GIT_REF_FORMAT_NORMAL = 0,
@ -488,7 +497,7 @@ typedef enum {
* @param name Reference name to be checked. * @param name Reference name to be checked.
* @param flags Flags to constrain name validation rules - see the * @param flags Flags to constrain name validation rules - see the
* GIT_REF_FORMAT constants above. * GIT_REF_FORMAT constants above.
* @return 0 on success, GIT_EBUFS if buffer is too small, EINVALIDSPEC * @return 0 on success, GIT_EBUFS if buffer is too small, GIT_EINVALIDSPEC
* or an error code. * or an error code.
*/ */
GIT_EXTERN(int) git_reference_normalize_name( GIT_EXTERN(int) git_reference_normalize_name(
@ -506,9 +515,9 @@ GIT_EXTERN(int) git_reference_normalize_name(
* If you pass `GIT_OBJ_ANY` as the target type, then the object * If you pass `GIT_OBJ_ANY` as the target type, then the object
* will be peeled until a non-tag object is met. * will be peeled until a non-tag object is met.
* *
* @param peeled Pointer to the peeled git_object * @param out Pointer to the peeled git_object
* @param ref The reference to be processed * @param ref The reference to be processed
* @param target_type The type of the requested object (GIT_OBJ_COMMIT, * @param type The type of the requested object (GIT_OBJ_COMMIT,
* GIT_OBJ_TAG, GIT_OBJ_TREE, GIT_OBJ_BLOB or GIT_OBJ_ANY). * GIT_OBJ_TAG, GIT_OBJ_TREE, GIT_OBJ_BLOB or GIT_OBJ_ANY).
* @return 0 on success, GIT_EAMBIGUOUS, GIT_ENOTFOUND or an error code * @return 0 on success, GIT_EAMBIGUOUS, GIT_ENOTFOUND or an error code
*/ */

View File

@ -55,7 +55,7 @@ GIT_EXTERN(int) git_refspec_force(const git_refspec *refspec);
/** /**
* Get the refspec's direction. * Get the refspec's direction.
* *
* @param the refspec * @param spec refspec
* @return GIT_DIRECTION_FETCH or GIT_DIRECTION_PUSH * @return GIT_DIRECTION_FETCH or GIT_DIRECTION_PUSH
*/ */
GIT_EXTERN(git_direction) git_refspec_direction(const git_refspec *spec); GIT_EXTERN(git_direction) git_refspec_direction(const git_refspec *spec);

View File

@ -25,13 +25,6 @@
GIT_BEGIN_DECL GIT_BEGIN_DECL
typedef int (*git_remote_rename_problem_cb)(const char *problematic_refspec, void *payload); typedef int (*git_remote_rename_problem_cb)(const char *problematic_refspec, void *payload);
/*
* TODO: This functions still need to be implemented:
* - _listcb/_foreach
* - _add
* - _rename
* - _del (needs support from config)
*/
/** /**
* Add a remote with the default fetch refspec to the repository's configuration. This * Add a remote with the default fetch refspec to the repository's configuration. This
@ -95,6 +88,14 @@ GIT_EXTERN(int) git_remote_load(git_remote **out, git_repository *repo, const ch
*/ */
GIT_EXTERN(int) git_remote_save(const git_remote *remote); GIT_EXTERN(int) git_remote_save(const git_remote *remote);
/**
* Get the remote's repository
*
* @param remote the remote
* @return a pointer to the repository
*/
GIT_EXTERN(git_repository *) git_remote_owner(const git_remote *remote);
/** /**
* Get the remote's name * Get the remote's name
* *
@ -247,19 +248,20 @@ GIT_EXTERN(int) git_remote_connect(git_remote *remote, git_direction direction);
GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload); GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload);
/** /**
* Download the packfile * Download and index the packfile
* *
* Negotiate what objects should be downloaded and download the * Connect to the remote if it hasn't been done yet, negotiate with
* packfile with those objects. The packfile is downloaded with a * the remote git which objects are missing, download and index the
* temporary filename, as it's final name is not known yet. If there * packfile.
* was no packfile needed (all the objects were available locally), *
* filename will be NULL and the function will return success. * The .idx file will be created and both it and the packfile with be
* renamed to their final name.
* *
* @param remote the remote to download from * @param remote the remote to download from
* @param progress_cb function to call with progress information. Be aware that * @param progress_cb function to call with progress information. Be aware that
* this is called inline with network and indexing operations, so performance * this is called inline with network and indexing operations, so performance
* may be affected. * may be affected.
* @param progress_payload payload for the progress callback * @param payload payload for the progress callback
* @return 0 or an error code * @return 0 or an error code
*/ */
GIT_EXTERN(int) git_remote_download( GIT_EXTERN(int) git_remote_download(
@ -320,7 +322,7 @@ GIT_EXTERN(int) git_remote_update_tips(git_remote *remote);
* Return whether a string is a valid remote URL * Return whether a string is a valid remote URL
* *
* @param url the url to check * @param url the url to check
* @param 1 if the url is valid, 0 otherwise * @return 1 if the url is valid, 0 otherwise
*/ */
GIT_EXTERN(int) git_remote_valid_url(const char *url); GIT_EXTERN(int) git_remote_valid_url(const char *url);

View File

@ -94,10 +94,14 @@ GIT_EXTERN(int) git_repository_discover(
* changes from the `stat` system call). (E.g. Searching in a user's home * changes from the `stat` system call). (E.g. Searching in a user's home
* directory "/home/user/source/" will not return "/.git/" as the found * directory "/home/user/source/" will not return "/.git/" as the found
* repo if "/" is a different filesystem than "/home".) * repo if "/" is a different filesystem than "/home".)
* * GIT_REPOSITORY_OPEN_BARE - Open repository as a bare repo regardless
* of core.bare config, and defer loading config file for faster setup.
* Unlike `git_repository_open_bare`, this can follow gitlinks.
*/ */
typedef enum { typedef enum {
GIT_REPOSITORY_OPEN_NO_SEARCH = (1 << 0), GIT_REPOSITORY_OPEN_NO_SEARCH = (1 << 0),
GIT_REPOSITORY_OPEN_CROSS_FS = (1 << 1), GIT_REPOSITORY_OPEN_CROSS_FS = (1 << 1),
GIT_REPOSITORY_OPEN_BARE = (1 << 2),
} git_repository_open_flag_t; } git_repository_open_flag_t;
/** /**
@ -178,7 +182,7 @@ GIT_EXTERN(int) git_repository_init(
* when initializing a new repo. Details of individual values are: * when initializing a new repo. Details of individual values are:
* *
* * BARE - Create a bare repository with no working directory. * * BARE - Create a bare repository with no working directory.
* * NO_REINIT - Return an EEXISTS error if the repo_path appears to * * NO_REINIT - Return an GIT_EEXISTS error if the repo_path appears to
* already be an git repository. * already be an git repository.
* * NO_DOTGIT_DIR - Normally a "/.git/" will be appended to the repo * * NO_DOTGIT_DIR - Normally a "/.git/" will be appended to the repo
* path for non-bare repos (if it is not already there), but * path for non-bare repos (if it is not already there), but
@ -471,7 +475,7 @@ GIT_EXTERN(int) git_repository_index(git_index **out, git_repository *repo);
* @param out Buffer to write data into or NULL to just read required size * @param out Buffer to write data into or NULL to just read required size
* @param len Length of `out` buffer in bytes * @param len Length of `out` buffer in bytes
* @param repo Repository to read prepared message from * @param repo Repository to read prepared message from
* @return GIT_ENOUTFOUND if no message exists, other value < 0 for other * @return GIT_ENOTFOUND if no message exists, other value < 0 for other
* errors, or total bytes in message (may be > `len`) on success * errors, or total bytes in message (may be > `len`) on success
*/ */
GIT_EXTERN(int) git_repository_message(char *out, size_t len, git_repository *repo); GIT_EXTERN(int) git_repository_message(char *out, size_t len, git_repository *repo);
@ -519,7 +523,7 @@ typedef int (*git_repository_mergehead_foreach_cb)(const git_oid *oid,
* *
* @param repo A repository object * @param repo A repository object
* @param callback Callback function * @param callback Callback function
* @param apyload Pointer to callback data (optional) * @param payload Pointer to callback data (optional)
* @return 0 on success, GIT_ENOTFOUND, GIT_EUSER or error * @return 0 on success, GIT_ENOTFOUND, GIT_EUSER or error
*/ */
GIT_EXTERN(int) git_repository_mergehead_foreach(git_repository *repo, GIT_EXTERN(int) git_repository_mergehead_foreach(git_repository *repo,

View File

@ -10,7 +10,6 @@
#include "common.h" #include "common.h"
#include "types.h" #include "types.h"
/** /**
* @file git2/revparse.h * @file git2/revparse.h
* @brief Git revision parsing routines * @brief Git revision parsing routines
@ -21,27 +20,37 @@
GIT_BEGIN_DECL GIT_BEGIN_DECL
/** /**
* Find a single object, as specified by a revision string. See `man gitrevisions`, * Find a single object, as specified by a revision string.
* or http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for *
* See `man gitrevisions`, or
* http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for
* information on the syntax accepted. * information on the syntax accepted.
* *
* The returned object should be released with `git_object_free` when no
* longer needed.
*
* @param out pointer to output object * @param out pointer to output object
* @param repo the repository to search in * @param repo the repository to search in
* @param spec the textual specification for an object * @param spec the textual specification for an object
* @return 0 on success, GIT_ENOTFOUND, GIT_EAMBIGUOUS, GIT_EINVALIDSPEC or an error code * @return 0 on success, GIT_ENOTFOUND, GIT_EAMBIGUOUS, GIT_EINVALIDSPEC or an error code
*/ */
GIT_EXTERN(int) git_revparse_single(git_object **out, git_repository *repo, const char *spec); GIT_EXTERN(int) git_revparse_single(
git_object **out, git_repository *repo, const char *spec);
/** /**
* Find a single object, as specified by a revision string. * Find a single object and intermediate reference by a revision string.
* See `man gitrevisions`, *
* or http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for * See `man gitrevisions`, or
* http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for
* information on the syntax accepted. * information on the syntax accepted.
* *
* In some cases (`@{<-n>}` or `<branchname>@{upstream}`), the expression may * In some cases (`@{<-n>}` or `<branchname>@{upstream}`), the expression may
* point to an intermediate reference. When such expressions are being passed * point to an intermediate reference. When such expressions are being passed
* in, `reference_out` will be valued as well. * in, `reference_out` will be valued as well.
* *
* The returned object should be released with `git_object_free` and the
* returned reference with `git_reference_free` when no longer needed.
*
* @param object_out pointer to output object * @param object_out pointer to output object
* @param reference_out pointer to output reference or NULL * @param reference_out pointer to output reference or NULL
* @param repo the repository to search in * @param repo the repository to search in
@ -76,17 +85,19 @@ typedef struct {
git_object *from; git_object *from;
/** The right element of the revspec; must be freed by the user */ /** The right element of the revspec; must be freed by the user */
git_object *to; git_object *to;
/** The intent of the revspec */ /** The intent of the revspec (i.e. `git_revparse_mode_t` flags) */
unsigned int flags; unsigned int flags;
} git_revspec; } git_revspec;
/** /**
* Parse a revision string for `from`, `to`, and intent. See `man gitrevisions` or * Parse a revision string for `from`, `to`, and intent.
* http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for information
* on the syntax accepted.
* *
* @param revspec Pointer to an user-allocated git_revspec struct where the result * See `man gitrevisions` or
* of the rev-parse will be stored * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for
* information on the syntax accepted.
*
* @param revspec Pointer to an user-allocated git_revspec struct where
* the result of the rev-parse will be stored
* @param repo the repository to search in * @param repo the repository to search in
* @param spec the rev-parse spec to parse * @param spec the rev-parse spec to parse
* @return 0 on success, GIT_INVALIDSPEC, GIT_ENOTFOUND, GIT_EAMBIGUOUS or an error code * @return 0 on success, GIT_INVALIDSPEC, GIT_ENOTFOUND, GIT_EAMBIGUOUS or an error code

View File

@ -231,6 +231,14 @@ GIT_EXTERN(void) git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode);
*/ */
GIT_EXTERN(int) git_revwalk_push_range(git_revwalk *walk, const char *range); GIT_EXTERN(int) git_revwalk_push_range(git_revwalk *walk, const char *range);
/**
* Simplify the history by first-parent
*
* No parents other than the first for each commit will be enqueued.
*/
GIT_EXTERN(void) git_revwalk_simplify_first_parent(git_revwalk *walk);
/** /**
* Free a revision walker previously allocated. * Free a revision walker previously allocated.
* *

View File

@ -48,6 +48,19 @@ GIT_EXTERN(int) git_signature_new(git_signature **out, const char *name, const c
*/ */
GIT_EXTERN(int) git_signature_now(git_signature **out, const char *name, const char *email); GIT_EXTERN(int) git_signature_now(git_signature **out, const char *name, const char *email);
/**
* Create a new action signature with default user and now timestamp.
*
* This looks up the user.name and user.email from the configuration and
* uses the current time as the timestamp, and creates a new signature
* based on that information. It will return GIT_ENOTFOUND if either the
* user.name or user.email are not set.
*
* @param out new signature
* @param repo repository pointer
* @return 0 on success, GIT_ENOTFOUND if config is missing, or error code
*/
GIT_EXTERN(int) git_signature_default(git_signature **out, git_repository *repo);
/** /**
* Create a copy of an existing signature. All internal strings are also * Create a copy of an existing signature. All internal strings are also

View File

@ -57,7 +57,7 @@ typedef enum {
GIT_EXTERN(int) git_stash_save( GIT_EXTERN(int) git_stash_save(
git_oid *out, git_oid *out,
git_repository *repo, git_repository *repo,
git_signature *stasher, const git_signature *stasher,
const char *message, const char *message,
unsigned int flags); unsigned int flags);
@ -89,7 +89,7 @@ typedef int (*git_stash_cb)(
* *
* @param repo Repository where to find the stash. * @param repo Repository where to find the stash.
* *
* @param callabck Callback to invoke per found stashed state. The most recent * @param callback Callback to invoke per found stashed state. The most recent
* stash state will be enumerated first. * stash state will be enumerated first.
* *
* @param payload Extra parameter to callback function. * @param payload Extra parameter to callback function.

View File

@ -42,6 +42,7 @@ typedef enum {
GIT_STATUS_WT_MODIFIED = (1u << 8), GIT_STATUS_WT_MODIFIED = (1u << 8),
GIT_STATUS_WT_DELETED = (1u << 9), GIT_STATUS_WT_DELETED = (1u << 9),
GIT_STATUS_WT_TYPECHANGE = (1u << 10), GIT_STATUS_WT_TYPECHANGE = (1u << 10),
GIT_STATUS_WT_RENAMED = (1u << 11),
GIT_STATUS_IGNORED = (1u << 14), GIT_STATUS_IGNORED = (1u << 14),
} git_status_t; } git_status_t;
@ -59,49 +60,24 @@ typedef int (*git_status_cb)(
const char *path, unsigned int status_flags, void *payload); const char *path, unsigned int status_flags, void *payload);
/** /**
* Gather file statuses and run a callback for each one. * Select the files on which to report status.
* *
* The callback is passed the path of the file, the status (a combination of * With `git_status_foreach_ext`, this will control which changes get
* the `git_status_t` values above) and the `payload` data pointer passed * callbacks. With `git_status_list_new`, these will control which
* into this function. * changes are included in the list.
* *
* If the callback returns a non-zero value, this function will stop looping * - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This roughly
* and return GIT_EUSER. * matches `git status --porcelain` regarding which files are
* * included and in what order.
* @param repo A repository object * - GIT_STATUS_SHOW_INDEX_ONLY only gives status based on HEAD to index
* @param callback The function to call on each file * comparison, not looking at working directory changes.
* @param payload Pointer to pass through to callback function * - GIT_STATUS_SHOW_WORKDIR_ONLY only gives status based on index to
* @return 0 on success, GIT_EUSER on non-zero callback, or error code * working directory comparison, not comparing the index to the HEAD.
*/
GIT_EXTERN(int) git_status_foreach(
git_repository *repo,
git_status_cb callback,
void *payload);
/**
* For extended status, select the files on which to report status.
*
* - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This is the
* rough equivalent of `git status --porcelain` where each file
* will receive a callback indicating its status in the index and
* in the workdir.
* - GIT_STATUS_SHOW_INDEX_ONLY will only make callbacks for index
* side of status. The status of the index contents relative to
* the HEAD will be given.
* - GIT_STATUS_SHOW_WORKDIR_ONLY will only make callbacks for the
* workdir side of status, reporting the status of workdir content
* relative to the index.
* - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR behaves like index-only
* followed by workdir-only, causing two callbacks to be issued
* per file (first index then workdir). This is slightly more
* efficient than making separate calls. This makes it easier to
* emulate the output of a plain `git status`.
*/ */
typedef enum { typedef enum {
GIT_STATUS_SHOW_INDEX_AND_WORKDIR = 0, GIT_STATUS_SHOW_INDEX_AND_WORKDIR = 0,
GIT_STATUS_SHOW_INDEX_ONLY = 1, GIT_STATUS_SHOW_INDEX_ONLY = 1,
GIT_STATUS_SHOW_WORKDIR_ONLY = 2, GIT_STATUS_SHOW_WORKDIR_ONLY = 2,
GIT_STATUS_SHOW_INDEX_THEN_WORKDIR = 3,
} git_status_show_t; } git_status_show_t;
/** /**
@ -110,26 +86,38 @@ typedef enum {
* - GIT_STATUS_OPT_INCLUDE_UNTRACKED says that callbacks should be made * - GIT_STATUS_OPT_INCLUDE_UNTRACKED says that callbacks should be made
* on untracked files. These will only be made if the workdir files are * on untracked files. These will only be made if the workdir files are
* included in the status "show" option. * included in the status "show" option.
* - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files should get * - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files get callbacks.
* callbacks. Again, these callbacks will only be made if the workdir * Again, these callbacks will only be made if the workdir files are
* files are included in the status "show" option. Right now, there is * included in the status "show" option.
* no option to include all files in directories that are ignored
* completely.
* - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback should be * - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback should be
* made even on unmodified files. * made even on unmodified files.
* - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that directories which * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that submodules should be
* appear to be submodules should just be skipped over. * skipped. This only applies if there are no pending typechanges to
* - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that the contents of * the submodule (either from or to another type).
* untracked directories should be included in the status. Normally if * - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that all files in
* an entire directory is new, then just the top-level directory will be * untracked directories should be included. Normally if an entire
* included (with a trailing slash on the entry name). Given this flag, * directory is new, then just the top-level directory is included (with
* the directory itself will not be included, but all the files in it * a trailing slash on the entry name). This flag says to include all
* will. * of the individual files in the directory instead.
* - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given path * - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given path
* will be treated as a literal path, and not as a pathspec. * should be treated as a literal path, and not as a pathspec pattern.
* - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS indicates that the contents of * - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS indicates that the contents of
* ignored directories should be included in the status. This is like * ignored directories should be included in the status. This is like
* doing `git ls-files -o -i --exclude-standard` with core git. * doing `git ls-files -o -i --exclude-standard` with core git.
* - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that rename detection
* should be processed between the head and the index and enables
* the GIT_STATUS_INDEX_RENAMED as a possible status flag.
* - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates that rename
* detection should be run between the index and the working directory
* and enabled GIT_STATUS_WT_RENAMED as a possible status flag.
* - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY overrides the native case
* sensitivity for the file system and forces the output to be in
* case-sensitive order
* - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY overrides the native case
* sensitivity for the file system and forces the output to be in
* case-insensitive order
* - GIT_STATUS_OPT_RENAMES_FROM_REWRITES indicates that rename detection
* should include rewritten files
* *
* Calling `git_status_foreach()` is like calling the extended version * Calling `git_status_foreach()` is like calling the extended version
* with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED, * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED,
@ -144,6 +132,11 @@ typedef enum {
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1u << 4), GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1u << 4),
GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1u << 5), GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1u << 5),
GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6), GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6),
GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX = (1u << 7),
GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8),
GIT_STATUS_OPT_SORT_CASE_SENSITIVELY = (1u << 9),
GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY = (1u << 10),
GIT_STATUS_OPT_RENAMES_FROM_REWRITES = (1u << 11),
} git_status_opt_t; } git_status_opt_t;
#define GIT_STATUS_OPT_DEFAULTS \ #define GIT_STATUS_OPT_DEFAULTS \
@ -177,6 +170,47 @@ typedef struct {
#define GIT_STATUS_OPTIONS_VERSION 1 #define GIT_STATUS_OPTIONS_VERSION 1
#define GIT_STATUS_OPTIONS_INIT {GIT_STATUS_OPTIONS_VERSION} #define GIT_STATUS_OPTIONS_INIT {GIT_STATUS_OPTIONS_VERSION}
/**
* A status entry, providing the differences between the file as it exists
* in HEAD and the index, and providing the differences between the index
* and the working directory.
*
* The `status` value provides the status flags for this file.
*
* The `head_to_index` value provides detailed information about the
* differences between the file in HEAD and the file in the index.
*
* The `index_to_workdir` value provides detailed information about the
* differences between the file in the index and the file in the
* working directory.
*/
typedef struct {
git_status_t status;
git_diff_delta *head_to_index;
git_diff_delta *index_to_workdir;
} git_status_entry;
/**
* Gather file statuses and run a callback for each one.
*
* The callback is passed the path of the file, the status (a combination of
* the `git_status_t` values above) and the `payload` data pointer passed
* into this function.
*
* If the callback returns a non-zero value, this function will stop looping
* and return GIT_EUSER.
*
* @param repo A repository object
* @param callback The function to call on each file
* @param payload Pointer to pass through to callback function
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/
GIT_EXTERN(int) git_status_foreach(
git_repository *repo,
git_status_cb callback,
void *payload);
/** /**
* Gather file status information and run callbacks as requested. * Gather file status information and run callbacks as requested.
* *
@ -203,18 +237,61 @@ GIT_EXTERN(int) git_status_foreach_ext(
* This is not quite the same as calling `git_status_foreach_ext()` with * This is not quite the same as calling `git_status_foreach_ext()` with
* the pathspec set to the specified path. * the pathspec set to the specified path.
* *
* @param status_flags The status value for the file * @param status_flags Output combination of git_status_t values for file
* @param repo A repository object * @param repo A repository object
* @param path The file to retrieve status for, rooted at the repo's workdir * @param path The file to retrieve status for relative to the repo workdir
* @return 0 on success, GIT_ENOTFOUND if the file is not found in the HEAD, * @return 0 on success, GIT_ENOTFOUND if the file is not found in the HEAD,
* index, and work tree, GIT_EINVALIDPATH if `path` points at a folder, * index, and work tree, GIT_EAMBIGUOUS if `path` matches multiple files
* GIT_EAMBIGUOUS if "path" matches multiple files, -1 on other error. * or if it refers to a folder, and -1 on other errors.
*/ */
GIT_EXTERN(int) git_status_file( GIT_EXTERN(int) git_status_file(
unsigned int *status_flags, unsigned int *status_flags,
git_repository *repo, git_repository *repo,
const char *path); const char *path);
/**
* Gather file status information and populate the `git_status_list`.
*
* @param out Pointer to store the status results in
* @param repo Repository object
* @param opts Status options structure
* @return 0 on success or error code
*/
GIT_EXTERN(int) git_status_list_new(
git_status_list **out,
git_repository *repo,
const git_status_options *opts);
/**
* Gets the count of status entries in this list.
*
* @param statuslist Existing status list object
* @return the number of status entries
*/
GIT_EXTERN(size_t) git_status_list_entrycount(
git_status_list *statuslist);
/**
* Get a pointer to one of the entries in the status list.
*
* The entry is not modifiable and should not be freed.
*
* @param statuslist Existing status list object
* @param idx Position of the entry
* @return Pointer to the entry; NULL if out of bounds
*/
GIT_EXTERN(const git_status_entry *) git_status_byindex(
git_status_list *statuslist,
size_t idx);
/**
* Free an existing status list
*
* @param statuslist Existing status list object
*/
GIT_EXTERN(void) git_status_list_free(
git_status_list *statuslist);
/** /**
* Test if the ignore rules apply to a given file. * Test if the ignore rules apply to a given file.
* *

View File

@ -14,51 +14,18 @@
/** /**
* @file git2/submodule.h * @file git2/submodule.h
* @brief Git submodule management utilities * @brief Git submodule management utilities
* @defgroup git_submodule Git submodule management routines
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
/**
* Opaque structure representing a submodule.
* *
* Submodule support in libgit2 builds a list of known submodules and keeps * Submodule support in libgit2 builds a list of known submodules and keeps
* it in the repository. The list is built from the .gitmodules file, the * it in the repository. The list is built from the .gitmodules file, the
* .git/config file, the index, and the HEAD tree. Items in the working * .git/config file, the index, and the HEAD tree. Items in the working
* directory that look like submodules (i.e. a git repo) but are not * directory that look like submodules (i.e. a git repo) but are not
* mentioned in those places won't be tracked. * mentioned in those places won't be tracked.
*/
typedef struct git_submodule git_submodule;
/**
* Values that could be specified for the update rule of a submodule.
* *
* Use the DEFAULT value if you have altered the update value via * @defgroup git_submodule Git submodule management routines
* `git_submodule_set_update()` and wish to reset to the original default. * @ingroup Git
* @{
*/ */
typedef enum { GIT_BEGIN_DECL
GIT_SUBMODULE_UPDATE_DEFAULT = -1,
GIT_SUBMODULE_UPDATE_CHECKOUT = 0,
GIT_SUBMODULE_UPDATE_REBASE = 1,
GIT_SUBMODULE_UPDATE_MERGE = 2,
GIT_SUBMODULE_UPDATE_NONE = 3
} git_submodule_update_t;
/**
* Values that could be specified for how closely to examine the
* working directory when getting submodule status.
*
* Use the DEFUALT value if you have altered the ignore value via
* `git_submodule_set_ignore()` and wish to reset to the original value.
*/
typedef enum {
GIT_SUBMODULE_IGNORE_DEFAULT = -1, /* reset to default */
GIT_SUBMODULE_IGNORE_NONE = 0, /* any change or untracked == dirty */
GIT_SUBMODULE_IGNORE_UNTRACKED = 1, /* dirty if tracked files change */
GIT_SUBMODULE_IGNORE_DIRTY = 2, /* only dirty if HEAD moved */
GIT_SUBMODULE_IGNORE_ALL = 3 /* never dirty */
} git_submodule_ignore_t;
/** /**
* Return codes for submodule status. * Return codes for submodule status.
@ -119,19 +86,9 @@ typedef enum {
GIT_SUBMODULE_STATUS_WD_UNTRACKED = (1u << 13), GIT_SUBMODULE_STATUS_WD_UNTRACKED = (1u << 13),
} git_submodule_status_t; } git_submodule_status_t;
#define GIT_SUBMODULE_STATUS__IN_FLAGS \ #define GIT_SUBMODULE_STATUS__IN_FLAGS 0x000Fu
(GIT_SUBMODULE_STATUS_IN_HEAD | \ #define GIT_SUBMODULE_STATUS__INDEX_FLAGS 0x0070u
GIT_SUBMODULE_STATUS_IN_INDEX | \ #define GIT_SUBMODULE_STATUS__WD_FLAGS 0x3F80u
GIT_SUBMODULE_STATUS_IN_CONFIG | \
GIT_SUBMODULE_STATUS_IN_WD)
#define GIT_SUBMODULE_STATUS__INDEX_FLAGS \
(GIT_SUBMODULE_STATUS_INDEX_ADDED | \
GIT_SUBMODULE_STATUS_INDEX_DELETED | \
GIT_SUBMODULE_STATUS_INDEX_MODIFIED)
#define GIT_SUBMODULE_STATUS__WD_FLAGS \
~(GIT_SUBMODULE_STATUS__IN_FLAGS | GIT_SUBMODULE_STATUS__INDEX_FLAGS)
#define GIT_SUBMODULE_STATUS_IS_UNMODIFIED(S) \ #define GIT_SUBMODULE_STATUS_IS_UNMODIFIED(S) \
(((S) & ~GIT_SUBMODULE_STATUS__IN_FLAGS) == 0) (((S) & ~GIT_SUBMODULE_STATUS__IN_FLAGS) == 0)
@ -359,9 +316,10 @@ GIT_EXTERN(const git_oid *) git_submodule_head_id(git_submodule *submodule);
GIT_EXTERN(const git_oid *) git_submodule_wd_id(git_submodule *submodule); GIT_EXTERN(const git_oid *) git_submodule_wd_id(git_submodule *submodule);
/** /**
* Get the ignore rule for the submodule. * Get the ignore rule that will be used for the submodule.
* *
* There are four ignore values: * These values control the behavior of `git_submodule_status()` for this
* submodule. There are four ignore values:
* *
* - **GIT_SUBMODULE_IGNORE_NONE** will consider any change to the contents * - **GIT_SUBMODULE_IGNORE_NONE** will consider any change to the contents
* of the submodule from a clean checkout to be dirty, including the * of the submodule from a clean checkout to be dirty, including the
@ -375,6 +333,13 @@ GIT_EXTERN(const git_oid *) git_submodule_wd_id(git_submodule *submodule);
* - **GIT_SUBMODULE_IGNORE_ALL** means not to open the submodule repo. * - **GIT_SUBMODULE_IGNORE_ALL** means not to open the submodule repo.
* The working directory will be consider clean so long as there is a * The working directory will be consider clean so long as there is a
* checked out version present. * checked out version present.
*
* plus the special **GIT_SUBMODULE_IGNORE_RESET** which can be used with
* `git_submodule_set_ignore()` to revert to the on-disk setting.
*
* @param submodule The submodule to check
* @return The current git_submodule_ignore_t valyue what will be used for
* this submodule.
*/ */
GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore( GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore(
git_submodule *submodule); git_submodule *submodule);
@ -382,15 +347,17 @@ GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore(
/** /**
* Set the ignore rule for the submodule. * Set the ignore rule for the submodule.
* *
* This sets the ignore rule in memory for the submodule. This will be used * This sets the in-memory ignore rule for the submodule which will
* for any following actions (such as `git_submodule_status()`) while the * control the behavior of `git_submodule_status()`.
* submodule is in memory. You should call `git_submodule_save()` if you
* want to persist the new ignore role.
* *
* Calling this again with GIT_SUBMODULE_IGNORE_DEFAULT or calling * To make changes persistent, call `git_submodule_save()` to write the
* `git_submodule_reload()` will revert the rule to the value that was in the * value to disk (in the ".gitmodules" and ".git/config" files).
* original config.
* *
* Call with `GIT_SUBMODULE_IGNORE_RESET` or call `git_submodule_reload()`
* to revert the in-memory rule to the value that is on disk.
*
* @param submodule The submodule to update
* @param ignore The new value for the ignore rule
* @return old value for ignore * @return old value for ignore
*/ */
GIT_EXTERN(git_submodule_ignore_t) git_submodule_set_ignore( GIT_EXTERN(git_submodule_ignore_t) git_submodule_set_ignore(
@ -398,7 +365,16 @@ GIT_EXTERN(git_submodule_ignore_t) git_submodule_set_ignore(
git_submodule_ignore_t ignore); git_submodule_ignore_t ignore);
/** /**
* Get the update rule for the submodule. * Get the update rule that will be used for the submodule.
*
* This value controls the behavior of the `git submodule update` command.
* There are four useful values documented with `git_submodule_update_t`
* plus the `GIT_SUBMODULE_UPDATE_RESET` which can be used to revert to
* the on-disk setting.
*
* @param submodule The submodule to check
* @return The current git_submodule_update_t value that will be used
* for this submodule.
*/ */
GIT_EXTERN(git_submodule_update_t) git_submodule_update( GIT_EXTERN(git_submodule_update_t) git_submodule_update(
git_submodule *submodule); git_submodule *submodule);
@ -406,13 +382,17 @@ GIT_EXTERN(git_submodule_update_t) git_submodule_update(
/** /**
* Set the update rule for the submodule. * Set the update rule for the submodule.
* *
* This sets the update rule in memory for the submodule. You should call * The initial value comes from the ".git/config" setting of
* `git_submodule_save()` if you want to persist the new update rule. * `submodule.$name.update` for this submodule (which is initialized from
* the ".gitmodules" file). Using this function sets the update rule in
* memory for the submodule. Call `git_submodule_save()` to write out the
* new update rule.
* *
* Calling this again with GIT_SUBMODULE_UPDATE_DEFAULT or calling * Calling this again with GIT_SUBMODULE_UPDATE_RESET or calling
* `git_submodule_reload()` will revert the rule to the value that was in the * `git_submodule_reload()` will revert the rule to the on disk value.
* original config.
* *
* @param submodule The submodule to update
* @param update The new value to use
* @return old value for update * @return old value for update
*/ */
GIT_EXTERN(git_submodule_update_t) git_submodule_set_update( GIT_EXTERN(git_submodule_update_t) git_submodule_set_update(
@ -481,7 +461,7 @@ GIT_EXTERN(int) git_submodule_sync(git_submodule *submodule);
* function will return distinct `git_repository` objects. This will only * function will return distinct `git_repository` objects. This will only
* work if the submodule is checked out into the working directory. * work if the submodule is checked out into the working directory.
* *
* @param subrepo Pointer to the submodule repo which was opened * @param repo Pointer to the submodule repo which was opened
* @param submodule Submodule to be opened * @param submodule Submodule to be opened
* @return 0 on success, <0 if submodule repo could not be opened. * @return 0 on success, <0 if submodule repo could not be opened.
*/ */
@ -531,7 +511,7 @@ GIT_EXTERN(int) git_submodule_status(
* This can be useful if you want to know if the submodule is present in the * This can be useful if you want to know if the submodule is present in the
* working directory at this point in time, etc. * working directory at this point in time, etc.
* *
* @param status Combination of first four `GIT_SUBMODULE_STATUS` flags * @param location_status Combination of first four `GIT_SUBMODULE_STATUS` flags
* @param submodule Submodule for which to get status * @param submodule Submodule for which to get status
* @return 0 on success, <0 on error * @return 0 on success, <0 on error
*/ */

View File

@ -20,6 +20,33 @@
*/ */
GIT_BEGIN_DECL GIT_BEGIN_DECL
/**
* Every iterator must have this struct as its first element, so the
* API can talk to it. You'd define your iterator as
*
* struct my_iterator {
* git_config_iterator parent;
* ...
* }
*
* and assign `iter->parent.backend` to your `git_config_backend`.
*/
struct git_config_iterator {
git_config_backend *backend;
unsigned int flags;
/**
* Return the current entry and advance the iterator. The
* memory belongs to the library.
*/
int (*next)(git_config_entry **entry, git_config_iterator *iter);
/**
* Free the iterator
*/
void (*free)(git_config_iterator *iter);
};
/** /**
* Generic backend that implements the interface to * Generic backend that implements the interface to
* access a configuration file * access a configuration file
@ -31,11 +58,10 @@ struct git_config_backend {
/* Open means open the file/database and parse if necessary */ /* Open means open the file/database and parse if necessary */
int (*open)(struct git_config_backend *, git_config_level_t level); int (*open)(struct git_config_backend *, git_config_level_t level);
int (*get)(const struct git_config_backend *, const char *key, const git_config_entry **entry); int (*get)(const struct git_config_backend *, const char *key, const git_config_entry **entry);
int (*get_multivar)(struct git_config_backend *, const char *key, const char *regexp, git_config_foreach_cb callback, void *payload);
int (*set)(struct git_config_backend *, const char *key, const char *value); int (*set)(struct git_config_backend *, const char *key, const char *value);
int (*set_multivar)(git_config_backend *cfg, const char *name, const char *regexp, const char *value); int (*set_multivar)(git_config_backend *cfg, const char *name, const char *regexp, const char *value);
int (*del)(struct git_config_backend *, const char *key); int (*del)(struct git_config_backend *, const char *key);
int (*foreach)(struct git_config_backend *, const char *, git_config_foreach_cb callback, void *payload); int (*iterator)(git_config_iterator **, struct git_config_backend *);
int (*refresh)(struct git_config_backend *); int (*refresh)(struct git_config_backend *);
void (*free)(struct git_config_backend *); void (*free)(struct git_config_backend *);
}; };

View File

@ -72,7 +72,6 @@ GIT_EXTERN(int) git_index_name_add(git_index *index,
* Remove all filename conflict entries. * Remove all filename conflict entries.
* *
* @param index an existing index object * @param index an existing index object
* @return 0 or an error code
*/ */
GIT_EXTERN(void) git_index_name_clear(git_index *index); GIT_EXTERN(void) git_index_name_clear(git_index *index);
@ -168,7 +167,6 @@ GIT_EXTERN(int) git_index_reuc_remove(git_index *index, size_t n);
* Remove all resolve undo entries from the index * Remove all resolve undo entries from the index
* *
* @param index an existing index object * @param index an existing index object
* @return 0 or an error code
*/ */
GIT_EXTERN(void) git_index_reuc_clear(git_index *index); GIT_EXTERN(void) git_index_reuc_clear(git_index *index);

View File

@ -48,12 +48,12 @@ struct git_odb_backend {
int (* read_header)( int (* read_header)(
size_t *, git_otype *, git_odb_backend *, const git_oid *); size_t *, git_otype *, git_odb_backend *, const git_oid *);
/* The writer may assume that the object /**
* has already been hashed and is passed * Write an object into the backend. The id of the object has
* in the first parameter. * already been calculated and is passed in.
*/ */
int (* write)( int (* write)(
git_oid *, git_odb_backend *, const void *, size_t, git_otype); git_odb_backend *, const git_oid *, const void *, size_t, git_otype);
int (* writestream)( int (* writestream)(
git_odb_stream **, git_odb_backend *, size_t, git_otype); git_odb_stream **, git_odb_backend *, size_t, git_otype);
@ -64,6 +64,16 @@ struct git_odb_backend {
int (* exists)( int (* exists)(
git_odb_backend *, const git_oid *); git_odb_backend *, const git_oid *);
/**
* If the backend implements a refreshing mechanism, it should be exposed
* through this endpoint. Each call to `git_odb_refresh()` will invoke it.
*
* However, the backend implementation should try to stay up-to-date as much
* as possible by itself as libgit2 will not automatically invoke
* `git_odb_refresh()`. For instance, a potential strategy for the backend
* implementation to achieve this could be to internally invoke this
* endpoint on failed lookups (ie. `exists()`, `read()`, `read_header()`).
*/
int (* refresh)(git_odb_backend *); int (* refresh)(git_odb_backend *);
int (* foreach)( int (* foreach)(

View File

@ -16,7 +16,7 @@
* *
* @param name the reference name * @param name the reference name
* @param oid the object id for a direct reference * @param oid the object id for a direct reference
* @param symbolic the target for a symbolic reference * @param peel the first non-tag object's OID, or NULL
* @return the created git_reference or NULL on error * @return the created git_reference or NULL on error
*/ */
GIT_EXTERN(git_reference *) git_reference__alloc( GIT_EXTERN(git_reference *) git_reference__alloc(
@ -28,7 +28,7 @@ GIT_EXTERN(git_reference *) git_reference__alloc(
* Create a new symbolic reference. * Create a new symbolic reference.
* *
* @param name the reference name * @param name the reference name
* @param symbolic the target for a symbolic reference * @param target the target for a symbolic reference
* @return the created git_reference or NULL on error * @return the created git_reference or NULL on error
*/ */
GIT_EXTERN(git_reference *) git_reference__alloc_symbolic( GIT_EXTERN(git_reference *) git_reference__alloc_symbolic(

View File

@ -36,14 +36,15 @@ typedef enum {
} git_credtype_t; } git_credtype_t;
/* The base structure for all credential types */ /* The base structure for all credential types */
typedef struct git_cred { typedef struct git_cred git_cred;
struct git_cred {
git_credtype_t credtype; git_credtype_t credtype;
void (*free)( void (*free)(git_cred *cred);
struct git_cred *cred); };
} git_cred;
/* A plaintext username and password */ /* A plaintext username and password */
typedef struct git_cred_userpass_plaintext { typedef struct {
git_cred parent; git_cred parent;
char *username; char *username;
char *password; char *password;
@ -51,10 +52,14 @@ typedef struct git_cred_userpass_plaintext {
#ifdef GIT_SSH #ifdef GIT_SSH
typedef LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*git_cred_sign_callback)); typedef LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*git_cred_sign_callback));
#else
typedef int (*git_cred_sign_callback)(void *, ...);
#endif
/* A ssh key file and passphrase */ /* A ssh key file and passphrase */
typedef struct git_cred_ssh_keyfile_passphrase { typedef struct git_cred_ssh_keyfile_passphrase {
git_cred parent; git_cred parent;
char *username;
char *publickey; char *publickey;
char *privatekey; char *privatekey;
char *passphrase; char *passphrase;
@ -63,12 +68,20 @@ typedef struct git_cred_ssh_keyfile_passphrase {
/* A ssh public key and authentication callback */ /* A ssh public key and authentication callback */
typedef struct git_cred_ssh_publickey { typedef struct git_cred_ssh_publickey {
git_cred parent; git_cred parent;
char *username;
char *publickey; char *publickey;
size_t publickey_len; size_t publickey_len;
void *sign_callback; void *sign_callback;
void *sign_data; void *sign_data;
} git_cred_ssh_publickey; } git_cred_ssh_publickey;
#endif
/**
* Check whether a credential object contains username information.
*
* @param cred object to check
* @return 1 if the credential object has non-NULL username, 0 otherwise
*/
GIT_EXTERN(int) git_cred_has_username(git_cred *cred);
/** /**
* Creates a new plain-text username and password credential object. * Creates a new plain-text username and password credential object.
@ -84,12 +97,12 @@ GIT_EXTERN(int) git_cred_userpass_plaintext_new(
const char *username, const char *username,
const char *password); const char *password);
#ifdef GIT_SSH
/** /**
* Creates a new ssh key file and passphrase credential object. * Creates a new ssh key file and passphrase credential object.
* The supplied credential parameter will be internally duplicated. * The supplied credential parameter will be internally duplicated.
* *
* @param out The newly created credential object. * @param out The newly created credential object.
* @param username username to use to authenticate
* @param publickey The path to the public key of the credential. * @param publickey The path to the public key of the credential.
* @param privatekey The path to the private key of the credential. * @param privatekey The path to the private key of the credential.
* @param passphrase The passphrase of the credential. * @param passphrase The passphrase of the credential.
@ -97,6 +110,7 @@ GIT_EXTERN(int) git_cred_userpass_plaintext_new(
*/ */
GIT_EXTERN(int) git_cred_ssh_keyfile_passphrase_new( GIT_EXTERN(int) git_cred_ssh_keyfile_passphrase_new(
git_cred **out, git_cred **out,
const char *username,
const char *publickey, const char *publickey,
const char *privatekey, const char *privatekey,
const char *passphrase); const char *passphrase);
@ -106,19 +120,20 @@ GIT_EXTERN(int) git_cred_ssh_keyfile_passphrase_new(
* The supplied credential parameter will be internally duplicated. * The supplied credential parameter will be internally duplicated.
* *
* @param out The newly created credential object. * @param out The newly created credential object.
* @param username username to use to authenticate
* @param publickey The bytes of the public key. * @param publickey The bytes of the public key.
* @param publickey_len The length of the public key in bytes. * @param publickey_len The length of the public key in bytes.
* @param sign_callback The callback method for authenticating. * @param sign_fn The callback method for authenticating.
* @param sign_data The abstract data sent to the sign_callback method. * @param sign_data The abstract data sent to the sign_callback method.
* @return 0 for success or an error code for failure * @return 0 for success or an error code for failure
*/ */
GIT_EXTERN(int) git_cred_ssh_publickey_new( GIT_EXTERN(int) git_cred_ssh_publickey_new(
git_cred **out, git_cred **out,
const char *username,
const char *publickey, const char *publickey,
size_t publickey_len, size_t publickey_len,
git_cred_sign_callback, git_cred_sign_callback sign_fn,
void *sign_data); void *sign_data);
#endif
/** /**
* Signature of a function which acquires a credential object. * Signature of a function which acquires a credential object.
@ -152,17 +167,21 @@ typedef enum {
typedef void (*git_transport_message_cb)(const char *str, int len, void *data); typedef void (*git_transport_message_cb)(const char *str, int len, void *data);
typedef struct git_transport { typedef struct git_transport git_transport;
struct git_transport {
unsigned int version; unsigned int version;
/* Set progress and error callbacks */ /* Set progress and error callbacks */
int (*set_callbacks)(struct git_transport *transport, int (*set_callbacks)(
git_transport *transport,
git_transport_message_cb progress_cb, git_transport_message_cb progress_cb,
git_transport_message_cb error_cb, git_transport_message_cb error_cb,
void *payload); void *payload);
/* Connect the transport to the remote repository, using the given /* Connect the transport to the remote repository, using the given
* direction. */ * direction. */
int (*connect)(struct git_transport *transport, int (*connect)(
git_transport *transport,
const char *url, const char *url,
git_cred_acquire_cb cred_acquire_cb, git_cred_acquire_cb cred_acquire_cb,
void *cred_acquire_payload, void *cred_acquire_payload,
@ -172,17 +191,19 @@ typedef struct git_transport {
/* This function may be called after a successful call to connect(). The /* This function may be called after a successful call to connect(). The
* provided callback is invoked for each ref discovered on the remote * provided callback is invoked for each ref discovered on the remote
* end. */ * end. */
int (*ls)(struct git_transport *transport, int (*ls)(
git_transport *transport,
git_headlist_cb list_cb, git_headlist_cb list_cb,
void *payload); void *payload);
/* Executes the push whose context is in the git_push object. */ /* Executes the push whose context is in the git_push object. */
int (*push)(struct git_transport *transport, git_push *push); int (*push)(git_transport *transport, git_push *push);
/* This function may be called after a successful call to connect(), when /* This function may be called after a successful call to connect(), when
* the direction is FETCH. The function performs a negotiation to calculate * the direction is FETCH. The function performs a negotiation to calculate
* the wants list for the fetch. */ * the wants list for the fetch. */
int (*negotiate_fetch)(struct git_transport *transport, int (*negotiate_fetch)(
git_transport *transport,
git_repository *repo, git_repository *repo,
const git_remote_head * const *refs, const git_remote_head * const *refs,
size_t count); size_t count);
@ -190,28 +211,29 @@ typedef struct git_transport {
/* This function may be called after a successful call to negotiate_fetch(), /* This function may be called after a successful call to negotiate_fetch(),
* when the direction is FETCH. This function retrieves the pack file for * when the direction is FETCH. This function retrieves the pack file for
* the fetch from the remote end. */ * the fetch from the remote end. */
int (*download_pack)(struct git_transport *transport, int (*download_pack)(
git_transport *transport,
git_repository *repo, git_repository *repo,
git_transfer_progress *stats, git_transfer_progress *stats,
git_transfer_progress_callback progress_cb, git_transfer_progress_callback progress_cb,
void *progress_payload); void *progress_payload);
/* Checks to see if the transport is connected */ /* Checks to see if the transport is connected */
int (*is_connected)(struct git_transport *transport); int (*is_connected)(git_transport *transport);
/* Reads the flags value previously passed into connect() */ /* Reads the flags value previously passed into connect() */
int (*read_flags)(struct git_transport *transport, int *flags); int (*read_flags)(git_transport *transport, int *flags);
/* Cancels any outstanding transport operation */ /* Cancels any outstanding transport operation */
void (*cancel)(struct git_transport *transport); void (*cancel)(git_transport *transport);
/* This function is the reverse of connect() -- it terminates the /* This function is the reverse of connect() -- it terminates the
* connection to the remote end. */ * connection to the remote end. */
int (*close)(struct git_transport *transport); int (*close)(git_transport *transport);
/* Frees/destructs the git_transport object. */ /* Frees/destructs the git_transport object. */
void (*free)(struct git_transport *transport); void (*free)(git_transport *transport);
} git_transport; };
#define GIT_TRANSPORT_VERSION 1 #define GIT_TRANSPORT_VERSION 1
#define GIT_TRANSPORT_INIT {GIT_TRANSPORT_VERSION} #define GIT_TRANSPORT_INIT {GIT_TRANSPORT_VERSION}
@ -299,35 +321,36 @@ typedef enum {
GIT_SERVICE_RECEIVEPACK = 4, GIT_SERVICE_RECEIVEPACK = 4,
} git_smart_service_t; } git_smart_service_t;
struct git_smart_subtransport; typedef struct git_smart_subtransport git_smart_subtransport;
typedef struct git_smart_subtransport_stream git_smart_subtransport_stream;
/* A stream used by the smart transport to read and write data /* A stream used by the smart transport to read and write data
* from a subtransport */ * from a subtransport */
typedef struct git_smart_subtransport_stream { struct git_smart_subtransport_stream {
/* The owning subtransport */ /* The owning subtransport */
struct git_smart_subtransport *subtransport; git_smart_subtransport *subtransport;
int (*read)( int (*read)(
struct git_smart_subtransport_stream *stream, git_smart_subtransport_stream *stream,
char *buffer, char *buffer,
size_t buf_size, size_t buf_size,
size_t *bytes_read); size_t *bytes_read);
int (*write)( int (*write)(
struct git_smart_subtransport_stream *stream, git_smart_subtransport_stream *stream,
const char *buffer, const char *buffer,
size_t len); size_t len);
void (*free)( void (*free)(
struct git_smart_subtransport_stream *stream); git_smart_subtransport_stream *stream);
} git_smart_subtransport_stream; };
/* An implementation of a subtransport which carries data for the /* An implementation of a subtransport which carries data for the
* smart transport */ * smart transport */
typedef struct git_smart_subtransport { struct git_smart_subtransport {
int (* action)( int (* action)(
git_smart_subtransport_stream **out, git_smart_subtransport_stream **out,
struct git_smart_subtransport *transport, git_smart_subtransport *transport,
const char *url, const char *url,
git_smart_service_t action); git_smart_service_t action);
@ -337,10 +360,10 @@ typedef struct git_smart_subtransport {
* *
* 1. UPLOADPACK_LS -> UPLOADPACK * 1. UPLOADPACK_LS -> UPLOADPACK
* 2. RECEIVEPACK_LS -> RECEIVEPACK */ * 2. RECEIVEPACK_LS -> RECEIVEPACK */
int (* close)(struct git_smart_subtransport *transport); int (*close)(git_smart_subtransport *transport);
void (* free)(struct git_smart_subtransport *transport); void (*free)(git_smart_subtransport *transport);
} git_smart_subtransport; };
/* A function which creates a new subtransport for the smart transport */ /* A function which creates a new subtransport for the smart transport */
typedef int (*git_smart_subtransport_cb)( typedef int (*git_smart_subtransport_cb)(

View File

@ -38,7 +38,7 @@ GIT_EXTERN(int) git_tree_lookup(
* *
* @see git_object_lookup_prefix * @see git_object_lookup_prefix
* *
* @param tree pointer to the looked up tree * @param out pointer to the looked up tree
* @param repo the repo to use when locating the tree. * @param repo the repo to use when locating the tree.
* @param id identity of the tree to locate. * @param id identity of the tree to locate.
* @param len the length of the short identifier * @param len the length of the short identifier
@ -136,7 +136,7 @@ GIT_EXTERN(const git_tree_entry *) git_tree_entry_byoid(
* *
* @param out Pointer where to store the tree entry * @param out Pointer where to store the tree entry
* @param root Previously loaded tree which is the root of the relative path * @param root Previously loaded tree which is the root of the relative path
* @param subtree_path Path to the contained entry * @param path Path to the contained entry
* @return 0 on success; GIT_ENOTFOUND if the path does not exist * @return 0 on success; GIT_ENOTFOUND if the path does not exist
*/ */
GIT_EXTERN(int) git_tree_entry_bypath( GIT_EXTERN(int) git_tree_entry_bypath(
@ -208,11 +208,11 @@ GIT_EXTERN(git_filemode_t) git_tree_entry_filemode(const git_tree_entry *entry);
GIT_EXTERN(int) git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2); GIT_EXTERN(int) git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2);
/** /**
* Convert a tree entry to the git_object it points too. * Convert a tree entry to the git_object it points to.
* *
* You must call `git_object_free()` on the object when you are done with it. * You must call `git_object_free()` on the object when you are done with it.
* *
* @param object pointer to the converted object * @param object_out pointer to the converted object
* @param repo repository where to lookup the pointed object * @param repo repository where to lookup the pointed object
* @param entry a tree entry * @param entry a tree entry
* @return 0 or an error code * @return 0 or an error code
@ -251,7 +251,7 @@ GIT_EXTERN(void) git_treebuilder_clear(git_treebuilder *bld);
/** /**
* Get the number of entries listed in a treebuilder * Get the number of entries listed in a treebuilder
* *
* @param tree a previously loaded treebuilder. * @param bld a previously loaded treebuilder.
* @return the number of entries in the treebuilder * @return the number of entries in the treebuilder
*/ */
GIT_EXTERN(unsigned int) git_treebuilder_entrycount(git_treebuilder *bld); GIT_EXTERN(unsigned int) git_treebuilder_entrycount(git_treebuilder *bld);

View File

@ -174,6 +174,9 @@ typedef struct git_reference_iterator git_reference_iterator;
/** Merge heads, the input to merge */ /** Merge heads, the input to merge */
typedef struct git_merge_head git_merge_head; typedef struct git_merge_head git_merge_head;
/** Representation of a status collection */
typedef struct git_status_list git_status_list;
/** Basic type of any Git reference. */ /** Basic type of any Git reference. */
typedef enum { typedef enum {
@ -226,6 +229,77 @@ typedef struct git_transfer_progress {
*/ */
typedef int (*git_transfer_progress_callback)(const git_transfer_progress *stats, void *payload); typedef int (*git_transfer_progress_callback)(const git_transfer_progress *stats, void *payload);
/**
* Opaque structure representing a submodule.
*/
typedef struct git_submodule git_submodule;
/**
* Submodule update values
*
* These values represent settings for the `submodule.$name.update`
* configuration value which says how to handle `git submodule update` for
* this submodule. The value is usually set in the ".gitmodules" file and
* copied to ".git/config" when the submodule is initialized.
*
* You can override this setting on a per-submodule basis with
* `git_submodule_set_update()` and write the changed value to disk using
* `git_submodule_save()`. If you have overwritten the value, you can
* revert it by passing `GIT_SUBMODULE_UPDATE_RESET` to the set function.
*
* The values are:
*
* - GIT_SUBMODULE_UPDATE_RESET: reset to the on-disk value.
* - GIT_SUBMODULE_UPDATE_CHECKOUT: the default; when a submodule is
* updated, checkout the new detached HEAD to the submodule directory.
* - GIT_SUBMODULE_UPDATE_REBASE: update by rebasing the current checked
* out branch onto the commit from the superproject.
* - GIT_SUBMODULE_UPDATE_MERGE: update by merging the commit in the
* superproject into the current checkout out branch of the submodule.
* - GIT_SUBMODULE_UPDATE_NONE: do not update this submodule even when
* the commit in the superproject is updated.
*/
typedef enum {
GIT_SUBMODULE_UPDATE_RESET = -1,
GIT_SUBMODULE_UPDATE_CHECKOUT = 1,
GIT_SUBMODULE_UPDATE_REBASE = 2,
GIT_SUBMODULE_UPDATE_MERGE = 3,
GIT_SUBMODULE_UPDATE_NONE = 4
} git_submodule_update_t;
/**
* Submodule ignore values
*
* These values represent settings for the `submodule.$name.ignore`
* configuration value which says how deeply to look at the working
* directory when getting submodule status.
*
* You can override this value in memory on a per-submodule basis with
* `git_submodule_set_ignore()` and can write the changed value to disk
* with `git_submodule_save()`. If you have overwritten the value, you
* can revert to the on disk value by using `GIT_SUBMODULE_IGNORE_RESET`.
*
* The values are:
*
* - GIT_SUBMODULE_IGNORE_RESET: reset to the on-disk value.
* - GIT_SUBMODULE_IGNORE_NONE: don't ignore any change - i.e. even an
* untracked file, will mark the submodule as dirty. Ignored files are
* still ignored, of course.
* - GIT_SUBMODULE_IGNORE_UNTRACKED: ignore untracked files; only changes
* to tracked files, or the index or the HEAD commit will matter.
* - GIT_SUBMODULE_IGNORE_DIRTY: ignore changes in the working directory,
* only considering changes if the HEAD of submodule has moved from the
* value in the superproject.
* - GIT_SUBMODULE_IGNORE_ALL: never check if the submodule is dirty
*/
typedef enum {
GIT_SUBMODULE_IGNORE_RESET = -1, /* reset to on-disk value */
GIT_SUBMODULE_IGNORE_NONE = 1, /* any change or untracked == dirty */
GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /* dirty if tracked files change */
GIT_SUBMODULE_IGNORE_DIRTY = 3, /* only dirty if HEAD moved */
GIT_SUBMODULE_IGNORE_ALL = 4 /* never dirty */
} git_submodule_ignore_t;
/** @} */ /** @} */
GIT_END_DECL GIT_END_DECL

View File

@ -7,9 +7,9 @@
#ifndef INCLUDE_git_version_h__ #ifndef INCLUDE_git_version_h__
#define INCLUDE_git_version_h__ #define INCLUDE_git_version_h__
#define LIBGIT2_VERSION "0.18.0" #define LIBGIT2_VERSION "0.19.0"
#define LIBGIT2_VER_MAJOR 0 #define LIBGIT2_VER_MAJOR 0
#define LIBGIT2_VER_MINOR 18 #define LIBGIT2_VER_MINOR 19
#define LIBGIT2_VER_REVISION 0 #define LIBGIT2_VER_REVISION 0
#endif #endif

32
script/cibuild.sh Executable file
View File

@ -0,0 +1,32 @@
#!/bin/sh
# Create a test repo which we can use for the online::push tests
mkdir $HOME/_temp
git init --bare $HOME/_temp/test.git
git daemon --listen=localhost --export-all --enable=receive-pack --base-path=$HOME/_temp $HOME/_temp 2>/dev/null &
export GITTEST_REMOTE_URL="git://localhost/test.git"
mkdir _build
cd _build
cmake .. -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS || exit $?
cmake --build . --target install || exit $?
ctest -V . || exit $?
# Now that we've tested the raw git protocol, let's set up ssh to we
# can do the push tests over it
killall git-daemon
sudo start ssh
ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q
cat ~/.ssh/id_rsa.pub >>~/.ssh/authorized_keys
ssh-keyscan -t rsa localhost >>~/.ssh/known_hosts
export GITTEST_REMOTE_URL="ssh://localhost/$HOME/_temp/test.git"
export GITTEST_REMOTE_USER=$USER
export GITTEST_REMOTE_SSH_KEY="$HOME/.ssh/id_rsa"
export GITTEST_REMOTE_SSH_PUBKEY="$HOME/.ssh/id_rsa.pub"
export GITTEST_REMOTE_SSH_PASSPHRASE=""
if [ -e ./libgit2_clar ]; then
./libgit2_clar -sonline::push
fi

View File

@ -30,37 +30,45 @@
#define git_array_init(a) \ #define git_array_init(a) \
do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0) do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0)
#define git_array_init_to_size(a, desired) \
do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0)
#define git_array_clear(a) \ #define git_array_clear(a) \
do { git__free((a).ptr); git_array_init(a); } while (0) do { git__free((a).ptr); git_array_init(a); } while (0)
#define GITERR_CHECK_ARRAY(a) GITERR_CHECK_ALLOC((a).ptr) #define GITERR_CHECK_ARRAY(a) GITERR_CHECK_ALLOC((a).ptr)
typedef git_array_t(void) git_array_generic_t; typedef git_array_t(char) git_array_generic_t;
/* use a generic array for growth so this can return the new item */ /* use a generic array for growth so this can return the new item */
GIT_INLINE(void *) git_array_grow(git_array_generic_t *a, size_t item_size) GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size)
{ {
git_array_generic_t *a = _a;
uint32_t new_size = (a->size < 8) ? 8 : a->asize * 3 / 2; uint32_t new_size = (a->size < 8) ? 8 : a->asize * 3 / 2;
void *new_array = git__realloc(a->ptr, new_size * item_size); char *new_array = git__realloc(a->ptr, new_size * item_size);
if (!new_array) { if (!new_array) {
git_array_clear(*a); git_array_clear(*a);
return NULL; return NULL;
} else { } else {
a->ptr = new_array; a->asize = new_size; a->size++; a->ptr = new_array; a->asize = new_size; a->size++;
return (((char *)a->ptr) + (a->size - 1) * item_size); return a->ptr + (a->size - 1) * item_size;
} }
} }
#define git_array_alloc(a) \ #define git_array_alloc(a) \
((a).size >= (a).asize) ? \ ((a).size >= (a).asize) ? \
git_array_grow((git_array_generic_t *)&(a), sizeof(*(a).ptr)) : \ 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) #define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : NULL)
#define git_array_pop(a) ((a).size ? &(a).ptr[--(a).size] : NULL)
#define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : NULL) #define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : NULL)
#define git_array_size(a) (a).size #define git_array_size(a) (a).size
#define git_array_valid_index(a, i) ((i) < (a).size)
#endif #endif

View File

@ -79,10 +79,14 @@ int git_attr_file__parse_buffer(
while (!error && *scan) { while (!error && *scan) {
/* allocate rule if needed */ /* allocate rule if needed */
if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) { if (!rule) {
if (!(rule = git__calloc(1, sizeof(git_attr_rule)))) {
error = -1; error = -1;
break; break;
} }
rule->match.flags = GIT_ATTR_FNMATCH_ALLOWNEG |
GIT_ATTR_FNMATCH_ALLOWMACRO;
}
/* parse the next "pattern attr attr attr" line */ /* parse the next "pattern attr attr attr" line */
if (!(error = git_attr_fnmatch__parse( if (!(error = git_attr_fnmatch__parse(
@ -351,8 +355,8 @@ int git_attr_fnmatch__parse(
if (parse_optimized_patterns(spec, pool, *base)) if (parse_optimized_patterns(spec, pool, *base))
return 0; return 0;
spec->flags = (spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE); spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING);
allow_space = (spec->flags != 0); allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0);
pattern = *base; pattern = *base;
@ -362,7 +366,7 @@ int git_attr_fnmatch__parse(
return GIT_ENOTFOUND; return GIT_ENOTFOUND;
} }
if (*pattern == '[') { if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) {
if (strncmp(pattern, "[attr]", 6) == 0) { if (strncmp(pattern, "[attr]", 6) == 0) {
spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO; spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
pattern += 6; pattern += 6;
@ -370,7 +374,7 @@ int git_attr_fnmatch__parse(
/* else a character range like [a-e]* which is accepted */ /* else a character range like [a-e]* which is accepted */
} }
if (*pattern == '!') { if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
pattern++; pattern++;
} }
@ -498,7 +502,7 @@ int git_attr_assignment__parse(
assert(assigns && !assigns->length); assert(assigns && !assigns->length);
assigns->_cmp = sort_by_hash_and_name; git_vector_set_cmp(assigns, sort_by_hash_and_name);
while (*scan && *scan != '\n') { while (*scan && *scan != '\n') {
const char *name_start, *value_start; const char *name_start, *value_start;

View File

@ -28,6 +28,12 @@
#define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6) #define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6)
#define GIT_ATTR_FNMATCH_ICASE (1U << 7) #define GIT_ATTR_FNMATCH_ICASE (1U << 7)
#define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8) #define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8)
#define GIT_ATTR_FNMATCH_ALLOWNEG (1U << 9)
#define GIT_ATTR_FNMATCH_ALLOWMACRO (1U << 10)
#define GIT_ATTR_FNMATCH__INCOMING \
(GIT_ATTR_FNMATCH_ALLOWSPACE | \
GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO)
extern const char *git_attr__true; extern const char *git_attr__true;
extern const char *git_attr__false; extern const char *git_attr__false;

75
src/bitvec.h Normal file
View File

@ -0,0 +1,75 @@
/*
* 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_bitvec_h__
#define INCLUDE_bitvec_h__
#include "util.h"
/*
* This is a silly little fixed length bit vector type that will store
* vectors of 64 bits or less directly in the structure and allocate
* memory for vectors longer than 64 bits. You can use the two versions
* transparently through the API and avoid heap allocation completely when
* using a short bit vector as a result.
*/
typedef struct {
size_t length;
union {
uint64_t *words;
uint64_t bits;
} u;
} git_bitvec;
GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity)
{
memset(bv, 0x0, sizeof(*bv));
if (capacity >= 64) {
bv->length = (capacity / 64) + 1;
bv->u.words = git__calloc(bv->length, sizeof(uint64_t));
if (!bv->u.words)
return -1;
}
return 0;
}
#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64))
#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits)
GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on)
{
uint64_t *word = GIT_BITVEC_WORD(bv, bit);
uint64_t mask = GIT_BITVEC_MASK(bit);
if (on)
*word |= mask;
else
*word &= ~mask;
}
GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit)
{
uint64_t *word = GIT_BITVEC_WORD(bv, bit);
return (*word & GIT_BITVEC_MASK(bit)) != 0;
}
GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv)
{
if (!bv->length)
bv->u.bits = 0;
else
memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t));
}
GIT_INLINE(void) git_bitvec_free(git_bitvec *bv)
{
if (bv->length)
git__free(bv->u.words);
}
#endif

View File

@ -60,10 +60,10 @@ int git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *b
(error = git_odb_open_wstream(&stream, odb, len, GIT_OBJ_BLOB)) < 0) (error = git_odb_open_wstream(&stream, odb, len, GIT_OBJ_BLOB)) < 0)
return error; return error;
if ((error = stream->write(stream, buffer, len)) == 0) if ((error = git_odb_stream_write(stream, buffer, len)) == 0)
error = stream->finalize_write(oid, stream); error = git_odb_stream_finalize_write(oid, stream);
stream->free(stream); git_odb_stream_free(stream);
return error; return error;
} }
@ -80,12 +80,12 @@ static int write_file_stream(
return error; return error;
if ((fd = git_futils_open_ro(path)) < 0) { if ((fd = git_futils_open_ro(path)) < 0) {
stream->free(stream); git_odb_stream_free(stream);
return -1; return -1;
} }
while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {
error = stream->write(stream, buffer, read_len); error = git_odb_stream_write(stream, buffer, read_len);
written += read_len; written += read_len;
} }
@ -97,14 +97,15 @@ static int write_file_stream(
} }
if (!error) if (!error)
error = stream->finalize_write(oid, stream); error = git_odb_stream_finalize_write(oid, stream);
stream->free(stream); git_odb_stream_free(stream);
return error; return error;
} }
static int write_file_filtered( static int write_file_filtered(
git_oid *oid, git_oid *oid,
git_off_t *size,
git_odb *odb, git_odb *odb,
const char *full_path, const char *full_path,
git_vector *filters) git_vector *filters)
@ -123,8 +124,11 @@ static int write_file_filtered(
git_buf_free(&source); git_buf_free(&source);
/* Write the file to disk if it was properly filtered */ /* Write the file to disk if it was properly filtered */
if (!error) if (!error) {
*size = dest.size;
error = git_odb_write(oid, odb, dest.ptr, dest.size, GIT_OBJ_BLOB); error = git_odb_write(oid, odb, dest.ptr, dest.size, GIT_OBJ_BLOB);
}
git_buf_free(&dest); git_buf_free(&dest);
return error; return error;
@ -152,21 +156,46 @@ static int write_symlink(
return error; return error;
} }
static int blob_create_internal(git_oid *oid, git_repository *repo, const char *content_path, const char *hint_path, bool try_load_filters) int git_blob__create_from_paths(
git_oid *oid,
struct stat *out_st,
git_repository *repo,
const char *content_path,
const char *hint_path,
mode_t hint_mode,
bool try_load_filters)
{ {
int error; int error;
struct stat st; struct stat st;
git_odb *odb = NULL; git_odb *odb = NULL;
git_off_t size; git_off_t size;
mode_t mode;
git_buf path = GIT_BUF_INIT;
assert(hint_path || !try_load_filters); assert(hint_path || !try_load_filters);
if ((error = git_path_lstat(content_path, &st)) < 0 || (error = git_repository_odb__weakptr(&odb, repo)) < 0) if (!content_path) {
return error; if (git_repository__ensure_not_bare(repo, "create blob from file") < 0)
return GIT_EBAREREPO;
if (git_buf_joinpath(
&path, git_repository_workdir(repo), hint_path) < 0)
return -1;
content_path = path.ptr;
}
if ((error = git_path_lstat(content_path, &st)) < 0 ||
(error = git_repository_odb(&odb, repo)) < 0)
goto done;
if (out_st)
memcpy(out_st, &st, sizeof(st));
size = st.st_size; size = st.st_size;
mode = hint_mode ? hint_mode : st.st_mode;
if (S_ISLNK(st.st_mode)) { if (S_ISLNK(mode)) {
error = write_symlink(oid, odb, content_path, (size_t)size); error = write_symlink(oid, odb, content_path, (size_t)size);
} else { } else {
git_vector write_filters = GIT_VECTOR_INIT; git_vector write_filters = GIT_VECTOR_INIT;
@ -187,7 +216,8 @@ static int blob_create_internal(git_oid *oid, git_repository *repo, const char *
error = write_file_stream(oid, odb, content_path, size); error = write_file_stream(oid, odb, content_path, size);
} else { } else {
/* We need to apply one or more filters */ /* We need to apply one or more filters */
error = write_file_filtered(oid, odb, content_path, &write_filters); error = write_file_filtered(
oid, &size, odb, content_path, &write_filters);
} }
git_filters_free(&write_filters); git_filters_free(&write_filters);
@ -207,34 +237,21 @@ static int blob_create_internal(git_oid *oid, git_repository *repo, const char *
*/ */
} }
done:
git_odb_free(odb);
git_buf_free(&path);
return error; return error;
} }
int git_blob_create_fromworkdir(git_oid *oid, git_repository *repo, const char *path) int git_blob_create_fromworkdir(
git_oid *oid, git_repository *repo, const char *path)
{ {
git_buf full_path = GIT_BUF_INIT; return git_blob__create_from_paths(oid, NULL, repo, NULL, path, 0, true);
const char *workdir;
int error;
if ((error = git_repository__ensure_not_bare(repo, "create blob from file")) < 0)
return error;
workdir = git_repository_workdir(repo);
if (git_buf_joinpath(&full_path, workdir, path) < 0) {
git_buf_free(&full_path);
return -1;
} }
error = blob_create_internal( int git_blob_create_fromdisk(
oid, repo, git_buf_cstr(&full_path), git_oid *oid, git_repository *repo, const char *path)
git_buf_cstr(&full_path) + strlen(workdir), true);
git_buf_free(&full_path);
return error;
}
int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *path)
{ {
int error; int error;
git_buf full_path = GIT_BUF_INIT; git_buf full_path = GIT_BUF_INIT;
@ -251,8 +268,8 @@ int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *pat
if (workdir && !git__prefixcmp(hintpath, workdir)) if (workdir && !git__prefixcmp(hintpath, workdir))
hintpath += strlen(workdir); hintpath += strlen(workdir);
error = blob_create_internal( error = git_blob__create_from_paths(
oid, repo, git_buf_cstr(&full_path), hintpath, true); oid, NULL, repo, git_buf_cstr(&full_path), hintpath, 0, true);
git_buf_free(&full_path); git_buf_free(&full_path);
return error; return error;
@ -272,11 +289,8 @@ int git_blob_create_fromchunks(
git_filebuf file = GIT_FILEBUF_INIT; git_filebuf file = GIT_FILEBUF_INIT;
git_buf path = GIT_BUF_INIT; git_buf path = GIT_BUF_INIT;
if (git_buf_join_n( if (git_buf_joinpath(
&path, '/', 3, &path, git_repository_path(repo), GIT_OBJECTS_DIR "streamed") < 0)
git_repository_path(repo),
GIT_OBJECTS_DIR,
"streamed") < 0)
goto cleanup; goto cleanup;
content = git__malloc(BUFFER_SIZE); content = git__malloc(BUFFER_SIZE);
@ -303,7 +317,8 @@ int git_blob_create_fromchunks(
if (git_filebuf_flush(&file) < 0) if (git_filebuf_flush(&file) < 0)
goto cleanup; goto cleanup;
error = blob_create_internal(oid, repo, file.path_lock, hintpath, hintpath != NULL); error = git_blob__create_from_paths(
oid, NULL, repo, file.path_lock, hintpath, 0, hintpath != NULL);
cleanup: cleanup:
git_buf_free(&path); git_buf_free(&path);

View File

@ -21,4 +21,13 @@ void git_blob__free(void *blob);
int git_blob__parse(void *blob, git_odb_object *obj); int git_blob__parse(void *blob, git_odb_object *obj);
int git_blob__getbuf(git_buf *buffer, git_blob *blob); int git_blob__getbuf(git_buf *buffer, git_blob *blob);
extern int git_blob__create_from_paths(
git_oid *out_oid,
struct stat *out_st,
git_repository *repo,
const char *full_path,
const char *hint_path,
mode_t hint_mode,
bool apply_filters);
#endif #endif

View File

@ -132,7 +132,7 @@ int git_branch_foreach(
{ {
git_reference_iterator *iter; git_reference_iterator *iter;
git_reference *ref; git_reference *ref;
int error; int error = 0;
if (git_reference_iterator_new(&iter, repo) < 0) if (git_reference_iterator_new(&iter, repo) < 0)
return -1; return -1;
@ -143,7 +143,6 @@ int git_branch_foreach(
if (callback(ref->name + strlen(GIT_REFS_HEADS_DIR), if (callback(ref->name + strlen(GIT_REFS_HEADS_DIR),
GIT_BRANCH_LOCAL, payload)) { GIT_BRANCH_LOCAL, payload)) {
error = GIT_EUSER; error = GIT_EUSER;
break;
} }
} }
@ -152,11 +151,14 @@ int git_branch_foreach(
if (callback(ref->name + strlen(GIT_REFS_REMOTES_DIR), if (callback(ref->name + strlen(GIT_REFS_REMOTES_DIR),
GIT_BRANCH_REMOTE, payload)) { GIT_BRANCH_REMOTE, payload)) {
error = GIT_EUSER; error = GIT_EUSER;
break;
} }
} }
git_reference_free(ref); git_reference_free(ref);
/* check if the callback has cancelled iteration */
if (error == GIT_EUSER)
break;
} }
if (error == GIT_ITEROVER) if (error == GIT_ITEROVER)

View File

@ -170,8 +170,14 @@ int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strings)
bool git_buf_text_is_binary(const git_buf *buf) bool git_buf_text_is_binary(const git_buf *buf)
{ {
const char *scan = buf->ptr, *end = buf->ptr + buf->size; const char *scan = buf->ptr, *end = buf->ptr + buf->size;
git_bom_t bom;
int printable = 0, nonprintable = 0; int printable = 0, nonprintable = 0;
scan += git_buf_text_detect_bom(&bom, buf, 0);
if (bom > GIT_BOM_UTF8)
return 1;
while (scan < end) { while (scan < end) {
unsigned char c = *scan++; unsigned char c = *scan++;
@ -262,7 +268,7 @@ bool git_buf_text_gather_stats(
while (scan < end) { while (scan < end) {
unsigned char c = *scan++; unsigned char c = *scan++;
if ((c > 0x1F && c < 0x7F) || c > 0x9f) if (c > 0x1F && c != 0x7F)
stats->printable++; stats->printable++;
else switch (c) { else switch (c) {
case '\0': case '\0':

View File

@ -259,6 +259,15 @@ void git_buf_truncate(git_buf *buf, size_t len)
} }
} }
void git_buf_shorten(git_buf *buf, size_t amount)
{
if (amount > buf->size)
amount = buf->size;
buf->size = buf->size - amount;
buf->ptr[buf->size] = '\0';
}
void git_buf_rtruncate_at_char(git_buf *buf, char separator) void git_buf_rtruncate_at_char(git_buf *buf, char separator)
{ {
ssize_t idx = git_buf_rfind_next(buf, separator); ssize_t idx = git_buf_rfind_next(buf, separator);

View File

@ -91,6 +91,7 @@ int git_buf_vprintf(git_buf *buf, const char *format, va_list ap);
void git_buf_clear(git_buf *buf); void git_buf_clear(git_buf *buf);
void git_buf_consume(git_buf *buf, const char *end); void git_buf_consume(git_buf *buf, const char *end);
void git_buf_truncate(git_buf *buf, size_t len); void git_buf_truncate(git_buf *buf, size_t len);
void git_buf_shorten(git_buf *buf, size_t amount);
void git_buf_rtruncate_at_char(git_buf *path, char separator); void git_buf_rtruncate_at_char(git_buf *path, char separator);
int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...); int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...);

View File

@ -54,8 +54,12 @@
#if defined (_MSC_VER) #if defined (_MSC_VER)
typedef unsigned char bool; typedef unsigned char bool;
# ifndef true
# define true 1 # define true 1
# endif
# ifndef false
# define false 0 # define false 0
# endif
#else #else
# include <stdbool.h> # include <stdbool.h>
#endif #endif

View File

@ -220,9 +220,11 @@ static int checkout_action_no_wd(
action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, NONE); action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, NONE);
break; break;
case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */ case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */
case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
break; break;
case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, CONFLICT);
break;
case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/ case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/
if (delta->new_file.mode == GIT_FILEMODE_TREE) if (delta->new_file.mode == GIT_FILEMODE_TREE)
action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
@ -244,10 +246,10 @@ static int checkout_action_wd_only(
bool remove = false; bool remove = false;
git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE; git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;
if (!git_pathspec_match_path( if (!git_pathspec__match(
pathspec, wd->path, pathspec, wd->path,
(data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
git_iterator_ignore_case(workdir), NULL)) git_iterator_ignore_case(workdir), NULL, NULL))
return 0; return 0;
/* check if item is tracked in the index but not in the checkout diff */ /* check if item is tracked in the index but not in the checkout diff */
@ -605,7 +607,7 @@ static int checkout_get_actions(
uint32_t *actions = NULL; uint32_t *actions = NULL;
if (data->opts.paths.count > 0 && if (data->opts.paths.count > 0 &&
git_pathspec_init(&pathspec, &data->opts.paths, &pathpool) < 0) git_pathspec__vinit(&pathspec, &data->opts.paths, &pathpool) < 0)
return -1; return -1;
if ((error = git_iterator_current(&wditem, workdir)) < 0 && if ((error = git_iterator_current(&wditem, workdir)) < 0 &&
@ -657,7 +659,7 @@ static int checkout_get_actions(
goto fail; goto fail;
} }
git_pathspec_free(&pathspec); git_pathspec__vfree(&pathspec);
git_pool_clear(&pathpool); git_pool_clear(&pathpool);
return 0; return 0;
@ -668,7 +670,7 @@ fail:
*actions_ptr = NULL; *actions_ptr = NULL;
git__free(actions); git__free(actions);
git_pathspec_free(&pathspec); git_pathspec__vfree(&pathspec);
git_pool_clear(&pathpool); git_pool_clear(&pathpool);
return error; return error;
@ -691,17 +693,14 @@ static int buffer_to_file(
buffer, path, file_open_flags, file_mode)) < 0) buffer, path, file_open_flags, file_mode)) < 0)
return error; return error;
if (st != NULL && (error = p_stat(path, st)) < 0) { if (st != NULL && (error = p_stat(path, st)) < 0)
giterr_set(GITERR_OS, "Error while statting '%s'", path); giterr_set(GITERR_OS, "Error statting '%s'", path);
return error;
}
if ((file_mode & 0100) != 0 && (error = p_chmod(path, file_mode)) < 0) { else if (GIT_PERMS_IS_EXEC(file_mode) &&
(error = p_chmod(path, file_mode)) < 0)
giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path); giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
return error;
}
return 0; return error;
} }
static int blob_content_to_file( static int blob_content_to_file(
@ -856,7 +855,7 @@ static int checkout_submodule(
return 0; return 0;
if ((error = git_futils_mkdir( if ((error = git_futils_mkdir(
file->path, git_repository_workdir(data->repo), file->path, data->opts.target_directory,
data->opts.dir_mode, GIT_MKDIR_PATH)) < 0) data->opts.dir_mode, GIT_MKDIR_PATH)) < 0)
return error; return error;
@ -1028,7 +1027,7 @@ static int checkout_deferred_remove(git_repository *repo, const char *path)
{ {
#if 0 #if 0
int error = git_futils_rmdir_r( int error = git_futils_rmdir_r(
path, git_repository_workdir(repo), GIT_RMDIR_EMPTY_PARENTS); path, data->opts.target_directory, GIT_RMDIR_EMPTY_PARENTS);
if (error == GIT_ENOTFOUND) { if (error == GIT_ENOTFOUND) {
error = 0; error = 0;
@ -1161,7 +1160,8 @@ static int checkout_data_init(
return -1; return -1;
} }
if ((error = git_repository__ensure_not_bare(repo, "checkout")) < 0) if ((!proposed || !proposed->target_directory) &&
(error = git_repository__ensure_not_bare(repo, "checkout")) < 0)
return error; return error;
data->repo = repo; data->repo = repo;
@ -1174,6 +1174,13 @@ static int checkout_data_init(
else else
memmove(&data->opts, proposed, sizeof(git_checkout_opts)); memmove(&data->opts, proposed, sizeof(git_checkout_opts));
if (!data->opts.target_directory)
data->opts.target_directory = git_repository_workdir(repo);
else if (!git_path_isdir(data->opts.target_directory) &&
(error = git_futils_mkdir(data->opts.target_directory, NULL,
GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0)
goto cleanup;
/* refresh config and index content unless NO_REFRESH is given */ /* refresh config and index content unless NO_REFRESH is given */
if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) { if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) {
git_config *cfg; git_config *cfg;
@ -1236,7 +1243,8 @@ static int checkout_data_init(
if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 || if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 ||
(error = git_pool_init(&data->pool, 1, 0)) < 0 || (error = git_pool_init(&data->pool, 1, 0)) < 0 ||
(error = git_buf_puts(&data->path, git_repository_workdir(repo))) < 0) (error = git_buf_puts(&data->path, data->opts.target_directory)) < 0 ||
(error = git_path_to_dir(&data->path)) < 0)
goto cleanup; goto cleanup;
data->workdir_len = git_buf_len(&data->path); data->workdir_len = git_buf_len(&data->path);
@ -1284,11 +1292,13 @@ int git_checkout_iterator(
GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE;
if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 || if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 ||
(error = git_iterator_for_workdir( (error = git_iterator_for_workdir_ext(
&workdir, data.repo, iterflags | GIT_ITERATOR_DONT_AUTOEXPAND, &workdir, data.repo, data.opts.target_directory,
iterflags | GIT_ITERATOR_DONT_AUTOEXPAND,
data.pfx, data.pfx)) < 0 || data.pfx, data.pfx)) < 0 ||
(error = git_iterator_for_tree( (error = git_iterator_for_tree(
&baseline, data.opts.baseline, iterflags, data.pfx, data.pfx)) < 0) &baseline, data.opts.baseline,
iterflags, data.pfx, data.pfx)) < 0)
goto cleanup; goto cleanup;
/* Should not have case insensitivity mismatch */ /* Should not have case insensitivity mismatch */
@ -1356,8 +1366,19 @@ int git_checkout_index(
int error; int error;
git_iterator *index_i; git_iterator *index_i;
if ((error = git_repository__ensure_not_bare(repo, "checkout index")) < 0) if (!index && !repo) {
return error; giterr_set(GITERR_CHECKOUT,
"Must provide either repository or index to checkout");
return -1;
}
if (index && repo && git_index_owner(index) != repo) {
giterr_set(GITERR_CHECKOUT,
"Index to checkout does not match repository");
return -1;
}
if (!repo)
repo = git_index_owner(index);
if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
return error; return error;
@ -1381,8 +1402,19 @@ int git_checkout_tree(
git_tree *tree = NULL; git_tree *tree = NULL;
git_iterator *tree_i = NULL; git_iterator *tree_i = NULL;
if ((error = git_repository__ensure_not_bare(repo, "checkout tree")) < 0) if (!treeish && !repo) {
return error; giterr_set(GITERR_CHECKOUT,
"Must provide either repository or tree to checkout");
return -1;
}
if (treeish && repo && git_object_owner(treeish) != repo) {
giterr_set(GITERR_CHECKOUT,
"Object to checkout does not match repository");
return -1;
}
if (!repo)
repo = git_object_owner(treeish);
if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) { if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) {
giterr_set( giterr_set(
@ -1407,8 +1439,7 @@ int git_checkout_head(
git_tree *head = NULL; git_tree *head = NULL;
git_iterator *head_i = NULL; git_iterator *head_i = NULL;
if ((error = git_repository__ensure_not_bare(repo, "checkout head")) < 0) assert(repo);
return error;
if (!(error = checkout_lookup_head_tree(&head, repo)) && if (!(error = checkout_lookup_head_tree(&head, repo)) &&
!(error = git_iterator_for_tree(&head_i, head, 0, NULL, NULL))) !(error = git_iterator_for_tree(&head_i, head, 0, NULL, NULL)))

View File

@ -418,7 +418,7 @@ static bool should_checkout(
return !git_repository_head_orphan(repo); return !git_repository_head_orphan(repo);
} }
static void normalize_options(git_clone_options *dst, const git_clone_options *src) static void normalize_options(git_clone_options *dst, const git_clone_options *src, git_repository_init_options *initOptions)
{ {
git_clone_options default_options = GIT_CLONE_OPTIONS_INIT; git_clone_options default_options = GIT_CLONE_OPTIONS_INIT;
if (!src) src = &default_options; if (!src) src = &default_options;
@ -427,6 +427,13 @@ static void normalize_options(git_clone_options *dst, const git_clone_options *s
/* Provide defaults for null pointers */ /* Provide defaults for null pointers */
if (!dst->remote_name) dst->remote_name = "origin"; if (!dst->remote_name) dst->remote_name = "origin";
if (!dst->init_options)
{
dst->init_options = initOptions;
initOptions->flags = GIT_REPOSITORY_INIT_MKPATH;
if (dst->bare)
initOptions->flags |= GIT_REPOSITORY_INIT_BARE;
}
} }
int git_clone( int git_clone(
@ -439,10 +446,11 @@ int git_clone(
git_repository *repo = NULL; git_repository *repo = NULL;
git_clone_options normOptions; git_clone_options normOptions;
int remove_directory_on_failure = 0; int remove_directory_on_failure = 0;
git_repository_init_options initOptions = GIT_REPOSITORY_INIT_OPTIONS_INIT;
assert(out && url && local_path); assert(out && url && local_path);
normalize_options(&normOptions, options); normalize_options(&normOptions, options, &initOptions);
GITERR_CHECK_VERSION(&normOptions, GIT_CLONE_OPTIONS_VERSION, "git_clone_options"); GITERR_CHECK_VERSION(&normOptions, GIT_CLONE_OPTIONS_VERSION, "git_clone_options");
/* Only clone to a new directory or an empty directory */ /* Only clone to a new directory or an empty directory */
@ -455,7 +463,7 @@ int git_clone(
/* Only remove the directory on failure if we create it */ /* Only remove the directory on failure if we create it */
remove_directory_on_failure = !git_path_exists(local_path); remove_directory_on_failure = !git_path_exists(local_path);
if (!(retcode = git_repository_init(&repo, local_path, normOptions.bare))) { if (!(retcode = git_repository_init_ext(&repo, local_path, normOptions.init_options))) {
if ((retcode = setup_remotes_and_fetch(repo, url, &normOptions)) < 0) { if ((retcode = setup_remotes_and_fetch(repo, url, &normOptions)) < 0) {
/* Failed to fetch; clean up */ /* Failed to fetch; clean up */
git_repository_free(repo); git_repository_free(repo);

View File

@ -19,30 +19,19 @@
#include <stdarg.h> #include <stdarg.h>
static void clear_parents(git_commit *commit)
{
size_t i;
for (i = 0; i < commit->parent_ids.length; ++i) {
git_oid *parent = git_vector_get(&commit->parent_ids, i);
git__free(parent);
}
git_vector_clear(&commit->parent_ids);
}
void git_commit__free(void *_commit) void git_commit__free(void *_commit)
{ {
git_commit *commit = _commit; git_commit *commit = _commit;
clear_parents(commit); git_array_clear(commit->parent_ids);
git_vector_free(&commit->parent_ids);
git_signature_free(commit->author); git_signature_free(commit->author);
git_signature_free(commit->committer); git_signature_free(commit->committer);
git__free(commit->raw_header);
git__free(commit->message); git__free(commit->message);
git__free(commit->message_encoding); git__free(commit->message_encoding);
git__free(commit); git__free(commit);
} }
@ -171,12 +160,35 @@ int git_commit_create(
int git_commit__parse(void *_commit, git_odb_object *odb_obj) int git_commit__parse(void *_commit, git_odb_object *odb_obj)
{ {
git_commit *commit = _commit; git_commit *commit = _commit;
const char *buffer = git_odb_object_data(odb_obj); const char *buffer_start = git_odb_object_data(odb_obj), *buffer;
const char *buffer_end = buffer + git_odb_object_size(odb_obj); const char *buffer_end = buffer_start + git_odb_object_size(odb_obj);
git_oid parent_id; git_oid parent_id;
uint32_t parent_count = 0;
size_t header_len;
if (git_vector_init(&commit->parent_ids, 4, NULL) < 0) /* find end-of-header (counting parents as we go) */
return -1; for (buffer = buffer_start; buffer < buffer_end; ++buffer) {
if (!strncmp("\n\n", buffer, 2)) {
++buffer;
break;
}
if (!strncmp("\nparent ", buffer, strlen("\nparent ")))
++parent_count;
}
header_len = buffer - buffer_start;
commit->raw_header = git__strndup(buffer_start, header_len);
GITERR_CHECK_ALLOC(commit->raw_header);
/* point "buffer" to header data */
buffer = commit->raw_header;
buffer_end = commit->raw_header + header_len;
if (parent_count < 1)
parent_count = 1;
git_array_init_to_size(commit->parent_ids, parent_count);
GITERR_CHECK_ARRAY(commit->parent_ids);
if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0) if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0)
goto bad_buffer; goto bad_buffer;
@ -186,13 +198,10 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj)
*/ */
while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) { while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) {
git_oid *new_id = git__malloc(sizeof(git_oid)); git_oid *new_id = git_array_alloc(commit->parent_ids);
GITERR_CHECK_ALLOC(new_id); GITERR_CHECK_ALLOC(new_id);
git_oid_cpy(new_id, &parent_id); git_oid_cpy(new_id, &parent_id);
if (git_vector_insert(&commit->parent_ids, new_id) < 0)
return -1;
} }
commit->author = git__malloc(sizeof(git_signature)); commit->author = git__malloc(sizeof(git_signature));
@ -208,8 +217,8 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj)
if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0) if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0)
return -1; return -1;
/* Parse add'l header entries until blank line found */ /* Parse add'l header entries */
while (buffer < buffer_end && *buffer != '\n') { while (buffer < buffer_end) {
const char *eoln = buffer; const char *eoln = buffer;
while (eoln < buffer_end && *eoln != '\n') while (eoln < buffer_end && *eoln != '\n')
++eoln; ++eoln;
@ -223,15 +232,18 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj)
if (eoln < buffer_end && *eoln == '\n') if (eoln < buffer_end && *eoln == '\n')
++eoln; ++eoln;
buffer = eoln; buffer = eoln;
} }
/* buffer is now at the end of the header, double-check and move forward into the message */ /* point "buffer" to data after header */
if (buffer < buffer_end && *buffer == '\n') buffer = git_odb_object_data(odb_obj);
buffer++; buffer_end = buffer + git_odb_object_size(odb_obj);
/* parse commit message */ buffer += header_len;
if (*buffer == '\n')
++buffer;
/* extract commit message */
if (buffer <= buffer_end) { if (buffer <= buffer_end) {
commit->message = git__strndup(buffer, buffer_end - buffer); commit->message = git__strndup(buffer, buffer_end - buffer);
GITERR_CHECK_ALLOC(commit->message); GITERR_CHECK_ALLOC(commit->message);
@ -255,9 +267,10 @@ GIT_COMMIT_GETTER(const git_signature *, author, commit->author)
GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer) GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer)
GIT_COMMIT_GETTER(const char *, message, commit->message) GIT_COMMIT_GETTER(const char *, message, commit->message)
GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding) GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding)
GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header)
GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time) GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time)
GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset) GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)commit->parent_ids.length) GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids))
GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id); GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id);
int git_commit_tree(git_tree **tree_out, const git_commit *commit) int git_commit_tree(git_tree **tree_out, const git_commit *commit)
@ -271,7 +284,7 @@ const git_oid *git_commit_parent_id(
{ {
assert(commit); assert(commit);
return git_vector_get(&commit->parent_ids, n); return git_array_get(commit->parent_ids, n);
} }
int git_commit_parent( int git_commit_parent(

View File

@ -10,14 +10,14 @@
#include "git2/commit.h" #include "git2/commit.h"
#include "tree.h" #include "tree.h"
#include "repository.h" #include "repository.h"
#include "vector.h" #include "array.h"
#include <time.h> #include <time.h>
struct git_commit { struct git_commit {
git_object object; git_object object;
git_vector parent_ids; git_array_t(git_oid) parent_ids;
git_oid tree_id; git_oid tree_id;
git_signature *author; git_signature *author;
@ -25,6 +25,7 @@ struct git_commit {
char *message_encoding; char *message_encoding;
char *message; char *message;
char *raw_header;
}; };
void git_commit__free(void *commit); void git_commit__free(void *commit);

View File

@ -36,7 +36,7 @@ git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_
git_commit_list *p; git_commit_list *p;
while ((p = *pp) != NULL) { while ((p = *pp) != NULL) {
if (git_commit_list_time_cmp(p->item, item) < 0) if (git_commit_list_time_cmp(p->item, item) > 0)
break; break;
pp = &p->next; pp = &p->next;

View File

@ -315,30 +315,241 @@ int git_config_refresh(git_config *cfg)
* Loop over all the variables * Loop over all the variables
*/ */
typedef struct {
git_config_iterator parent;
git_config_iterator *current;
const git_config *cfg;
regex_t regex;
int has_regex;
size_t i;
} all_iter;
static int find_next_backend(size_t *out, const git_config *cfg, size_t i)
{
file_internal *internal;
for (; i > 0; --i) {
internal = git_vector_get(&cfg->files, i - 1);
if (!internal || !internal->file)
continue;
*out = i;
return 0;
}
return -1;
}
static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter)
{
all_iter *iter = (all_iter *) _iter;
file_internal *internal;
git_config_backend *backend;
size_t i;
int error = 0;
if (iter->current != NULL &&
(error = iter->current->next(entry, iter->current)) == 0) {
return 0;
}
if (error < 0 && error != GIT_ITEROVER)
return error;
do {
if (find_next_backend(&i, iter->cfg, iter->i) < 0)
return GIT_ITEROVER;
internal = git_vector_get(&iter->cfg->files, i - 1);
backend = internal->file;
iter->i = i - 1;
if (iter->current)
iter->current->free(iter->current);
iter->current = NULL;
error = backend->iterator(&iter->current, backend);
if (error == GIT_ENOTFOUND)
continue;
if (error < 0)
return error;
error = iter->current->next(entry, iter->current);
/* If this backend is empty, then keep going */
if (error == GIT_ITEROVER)
continue;
return error;
} while(1);
return GIT_ITEROVER;
}
static int all_iter_glob_next(git_config_entry **entry, git_config_iterator *_iter)
{
int error;
all_iter *iter = (all_iter *) _iter;
/*
* We use the "normal" function to grab the next one across
* backends and then apply the regex
*/
while ((error = all_iter_next(entry, _iter)) == 0) {
/* skip non-matching keys if regexp was provided */
if (regexec(&iter->regex, (*entry)->name, 0, NULL, 0) != 0)
continue;
/* and simply return if we like the entry's name */
return 0;
}
return error;
}
static void all_iter_free(git_config_iterator *_iter)
{
all_iter *iter = (all_iter *) _iter;
if (iter->current)
iter->current->free(iter->current);
git__free(iter);
}
static void all_iter_glob_free(git_config_iterator *_iter)
{
all_iter *iter = (all_iter *) _iter;
regfree(&iter->regex);
all_iter_free(_iter);
}
int git_config_iterator_new(git_config_iterator **out, const git_config *cfg)
{
all_iter *iter;
iter = git__calloc(1, sizeof(all_iter));
GITERR_CHECK_ALLOC(iter);
iter->parent.free = all_iter_free;
iter->parent.next = all_iter_next;
iter->i = cfg->files.length;
iter->cfg = cfg;
*out = (git_config_iterator *) iter;
return 0;
}
int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp)
{
all_iter *iter;
int result;
if (regexp == NULL)
return git_config_iterator_new(out, cfg);
iter = git__calloc(1, sizeof(all_iter));
GITERR_CHECK_ALLOC(iter);
if ((result = regcomp(&iter->regex, regexp, REG_EXTENDED)) < 0) {
giterr_set_regex(&iter->regex, result);
regfree(&iter->regex);
return -1;
}
iter->parent.next = all_iter_glob_next;
iter->parent.free = all_iter_glob_free;
iter->i = cfg->files.length;
iter->cfg = cfg;
*out = (git_config_iterator *) iter;
return 0;
}
int git_config_foreach( int git_config_foreach(
const git_config *cfg, git_config_foreach_cb cb, void *payload) const git_config *cfg, git_config_foreach_cb cb, void *payload)
{ {
return git_config_foreach_match(cfg, NULL, cb, payload); return git_config_foreach_match(cfg, NULL, cb, payload);
} }
int git_config_backend_foreach_match(
git_config_backend *backend,
const char *regexp,
int (*fn)(const git_config_entry *, void *),
void *data)
{
git_config_entry *entry;
git_config_iterator* iter;
regex_t regex;
int result = 0;
if (regexp != NULL) {
if ((result = regcomp(&regex, regexp, REG_EXTENDED)) < 0) {
giterr_set_regex(&regex, result);
regfree(&regex);
return -1;
}
}
if ((result = backend->iterator(&iter, backend)) < 0) {
iter = NULL;
return -1;
}
while(!(iter->next(&entry, iter) < 0)) {
/* skip non-matching keys if regexp was provided */
if (regexp && regexec(&regex, entry->name, 0, NULL, 0) != 0)
continue;
/* abort iterator on non-zero return value */
if (fn(entry, data)) {
giterr_clear();
result = GIT_EUSER;
goto cleanup;
}
}
cleanup:
if (regexp != NULL)
regfree(&regex);
iter->free(iter);
return result;
}
int git_config_foreach_match( int git_config_foreach_match(
const git_config *cfg, const git_config *cfg,
const char *regexp, const char *regexp,
git_config_foreach_cb cb, git_config_foreach_cb cb,
void *payload) void *payload)
{ {
int ret = 0; int error;
size_t i; git_config_iterator *iter;
file_internal *internal; git_config_entry *entry;
git_config_backend *file;
for (i = 0; i < cfg->files.length && ret == 0; ++i) { if ((error = git_config_iterator_glob_new(&iter, cfg, regexp)) < 0)
internal = git_vector_get(&cfg->files, i); return error;
file = internal->file;
ret = file->foreach(file, regexp, cb, payload); while ((error = git_config_next(&entry, iter)) == 0) {
if(cb(entry, payload)) {
giterr_clear();
error = GIT_EUSER;
break;
}
} }
return ret; git_config_iterator_free(iter);
if (error == GIT_ITEROVER)
error = 0;
return error;
} }
/************** /**************
@ -528,31 +739,114 @@ int git_config_get_entry(const git_config_entry **out, const git_config *cfg, co
return config_error_notfound(name); return config_error_notfound(name);
} }
int git_config_get_multivar( int git_config_get_multivar_foreach(
const git_config *cfg, const char *name, const char *regexp, const git_config *cfg, const char *name, const char *regexp,
git_config_foreach_cb cb, void *payload) git_config_foreach_cb cb, void *payload)
{ {
file_internal *internal; int err, found;
git_config_backend *file; git_config_iterator *iter;
int ret = GIT_ENOTFOUND; git_config_entry *entry;
size_t i;
/* if ((err = git_config_multivar_iterator_new(&iter, cfg, name, regexp)) < 0)
* This loop runs the "wrong" way 'round because we need to return err;
* look at every value from the most general to most specific
*/
for (i = cfg->files.length; i > 0; --i) {
internal = git_vector_get(&cfg->files, i - 1);
if (!internal || !internal->file)
continue;
file = internal->file;
ret = file->get_multivar(file, name, regexp, cb, payload); found = 0;
if (ret < 0 && ret != GIT_ENOTFOUND) while ((err = iter->next(&entry, iter)) == 0) {
return ret; found = 1;
if(cb(entry, payload)) {
iter->free(iter);
return GIT_EUSER;
}
} }
return (ret == GIT_ENOTFOUND) ? config_error_notfound(name) : 0; iter->free(iter);
if (err == GIT_ITEROVER)
err = 0;
if (found == 0 && err == 0)
err = config_error_notfound(name);
return err;
}
typedef struct {
git_config_iterator parent;
git_config_iterator *iter;
char *name;
regex_t regex;
int have_regex;
} multivar_iter;
static int multivar_iter_next(git_config_entry **entry, git_config_iterator *_iter)
{
multivar_iter *iter = (multivar_iter *) _iter;
int error = 0;
while ((error = iter->iter->next(entry, iter->iter)) == 0) {
if (git__strcmp(iter->name, (*entry)->name))
continue;
if (!iter->have_regex)
return 0;
if (regexec(&iter->regex, (*entry)->value, 0, NULL, 0) == 0)
return 0;
}
return error;
}
void multivar_iter_free(git_config_iterator *_iter)
{
multivar_iter *iter = (multivar_iter *) _iter;
iter->iter->free(iter->iter);
git__free(iter->name);
regfree(&iter->regex);
git__free(iter);
}
int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp)
{
multivar_iter *iter = NULL;
git_config_iterator *inner = NULL;
int error;
if ((error = git_config_iterator_new(&inner, cfg)) < 0)
return error;
iter = git__calloc(1, sizeof(multivar_iter));
GITERR_CHECK_ALLOC(iter);
if ((error = git_config__normalize_name(name, &iter->name)) < 0)
goto on_error;
if (regexp != NULL) {
error = regcomp(&iter->regex, regexp, REG_EXTENDED);
if (error < 0) {
giterr_set_regex(&iter->regex, error);
error = -1;
regfree(&iter->regex);
goto on_error;
}
iter->have_regex = 1;
}
iter->iter = inner;
iter->parent.free = multivar_iter_free;
iter->parent.next = multivar_iter_next;
*out = (git_config_iterator *) iter;
return 0;
on_error:
inner->free(inner);
git__free(iter);
return error;
} }
int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value)
@ -568,6 +862,16 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regex
return file->set_multivar(file, name, regexp, value); return file->set_multivar(file, name, regexp, value);
} }
int git_config_next(git_config_entry **entry, git_config_iterator *iter)
{
return iter->next(entry, iter);
}
void git_config_iterator_free(git_config_iterator *iter)
{
iter->free(iter);
}
static int git_config__find_file_to_path( static int git_config__find_file_to_path(
char *out, size_t outlen, int (*find)(git_buf *buf)) char *out, size_t outlen, int (*find)(git_buf *buf))
{ {
@ -811,6 +1115,41 @@ fail_parse:
return -1; return -1;
} }
/* Take something the user gave us and make it nice for our hash function */
int git_config__normalize_name(const char *in, char **out)
{
char *name, *fdot, *ldot;
assert(in && out);
name = git__strdup(in);
GITERR_CHECK_ALLOC(name);
fdot = strchr(name, '.');
ldot = strrchr(name, '.');
if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1])
goto invalid;
/* Validate and downcase up to first dot and after last dot */
if (git_config_file_normalize_section(name, fdot) < 0 ||
git_config_file_normalize_section(ldot + 1, NULL) < 0)
goto invalid;
/* If there is a middle range, make sure it doesn't have newlines */
while (fdot < ldot)
if (*fdot++ == '\n')
goto invalid;
*out = name;
return 0;
invalid:
git__free(name);
giterr_set(GITERR_CONFIG, "Invalid config item name '%s'", in);
return GIT_EINVALIDSPEC;
}
struct rename_data { struct rename_data {
git_config *config; git_config *config;
git_buf *name; git_buf *name;

View File

@ -49,4 +49,7 @@ extern int git_config_rename_section(
*/ */
extern int git_config_file__ondisk(struct git_config_backend **out, const char *path); extern int git_config_file__ondisk(struct git_config_backend **out, const char *path);
extern int git_config__normalize_name(const char *in, char **out);
#endif #endif

View File

@ -27,6 +27,13 @@ typedef struct cvar_t {
git_config_entry *entry; git_config_entry *entry;
} cvar_t; } cvar_t;
typedef struct git_config_file_iter {
git_config_iterator parent;
git_strmap_iter iter;
cvar_t* next_var;
} git_config_file_iter;
#define CVAR_LIST_HEAD(list) ((list)->head) #define CVAR_LIST_HEAD(list) ((list)->head)
#define CVAR_LIST_TAIL(list) ((list)->tail) #define CVAR_LIST_TAIL(list) ((list)->tail)
@ -129,41 +136,6 @@ int git_config_file_normalize_section(char *start, char *end)
return 0; return 0;
} }
/* Take something the user gave us and make it nice for our hash function */
static int normalize_name(const char *in, char **out)
{
char *name, *fdot, *ldot;
assert(in && out);
name = git__strdup(in);
GITERR_CHECK_ALLOC(name);
fdot = strchr(name, '.');
ldot = strrchr(name, '.');
if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1])
goto invalid;
/* Validate and downcase up to first dot and after last dot */
if (git_config_file_normalize_section(name, fdot) < 0 ||
git_config_file_normalize_section(ldot + 1, NULL) < 0)
goto invalid;
/* If there is a middle range, make sure it doesn't have newlines */
while (fdot < ldot)
if (*fdot++ == '\n')
goto invalid;
*out = name;
return 0;
invalid:
git__free(name);
giterr_set(GITERR_CONFIG, "Invalid config item name '%s'", in);
return GIT_EINVALIDSPEC;
}
static void free_vars(git_strmap *values) static void free_vars(git_strmap *values)
{ {
cvar_t *var = NULL; cvar_t *var = NULL;
@ -247,51 +219,56 @@ static void backend_free(git_config_backend *_backend)
git__free(backend); git__free(backend);
} }
static int file_foreach( static void config_iterator_free(
git_config_backend *backend, git_config_iterator* iter)
const char *regexp, {
int (*fn)(const git_config_entry *, void *), git__free(iter);
void *data) }
static int config_iterator_next(
git_config_entry **entry,
git_config_iterator *iter)
{
git_config_file_iter *it = (git_config_file_iter *) iter;
diskfile_backend *b = (diskfile_backend *) it->parent.backend;
int err = 0;
cvar_t * var;
if (it->next_var == NULL) {
err = git_strmap_next((void**) &var, &(it->iter), b->values);
} else {
var = it->next_var;
}
if (err < 0) {
it->next_var = NULL;
return err;
}
*entry = var->entry;
it->next_var = CVAR_LIST_NEXT(var);
return 0;
}
static int config_iterator_new(
git_config_iterator **iter,
struct git_config_backend* backend)
{ {
diskfile_backend *b = (diskfile_backend *)backend; diskfile_backend *b = (diskfile_backend *)backend;
cvar_t *var, *next_var; git_config_file_iter *it = git__calloc(1, sizeof(git_config_file_iter));
const char *key;
regex_t regex; GITERR_CHECK_ALLOC(it);
int result = 0;
it->parent.backend = backend;
it->iter = git_strmap_begin(b->values);
it->next_var = NULL;
it->parent.next = config_iterator_next;
it->parent.free = config_iterator_free;
*iter = (git_config_iterator *) it;
if (!b->values)
return 0; return 0;
if (regexp != NULL) {
if ((result = regcomp(&regex, regexp, REG_EXTENDED)) < 0) {
giterr_set_regex(&regex, result);
regfree(&regex);
return -1;
}
}
git_strmap_foreach(b->values, key, var,
for (; var != NULL; var = next_var) {
next_var = CVAR_LIST_NEXT(var);
/* skip non-matching keys if regexp was provided */
if (regexp && regexec(&regex, key, 0, NULL, 0) != 0)
continue;
/* abort iterator on non-zero return value */
if (fn(var->entry, data)) {
giterr_clear();
result = GIT_EUSER;
goto cleanup;
}
}
);
cleanup:
if (regexp != NULL)
regfree(&regex);
return result;
} }
static int config_set(git_config_backend *cfg, const char *name, const char *value) static int config_set(git_config_backend *cfg, const char *name, const char *value)
@ -302,7 +279,7 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val
khiter_t pos; khiter_t pos;
int rval, ret; int rval, ret;
if ((rval = normalize_name(name, &key)) < 0) if ((rval = git_config__normalize_name(name, &key)) < 0)
return rval; return rval;
/* /*
@ -385,7 +362,7 @@ static int config_get(const git_config_backend *cfg, const char *name, const git
khiter_t pos; khiter_t pos;
int error; int error;
if ((error = normalize_name(name, &key)) < 0) if ((error = git_config__normalize_name(name, &key)) < 0)
return error; return error;
pos = git_strmap_lookup_index(b->values, key); pos = git_strmap_lookup_index(b->values, key);
@ -400,70 +377,6 @@ static int config_get(const git_config_backend *cfg, const char *name, const git
return 0; return 0;
} }
static int config_get_multivar(
git_config_backend *cfg,
const char *name,
const char *regex_str,
int (*fn)(const git_config_entry *, void *),
void *data)
{
cvar_t *var;
diskfile_backend *b = (diskfile_backend *)cfg;
char *key;
khiter_t pos;
int error;
if ((error = normalize_name(name, &key)) < 0)
return error;
pos = git_strmap_lookup_index(b->values, key);
git__free(key);
if (!git_strmap_valid_index(b->values, pos))
return GIT_ENOTFOUND;
var = git_strmap_value_at(b->values, pos);
if (regex_str != NULL) {
regex_t regex;
int result;
/* regex matching; build the regex */
result = regcomp(&regex, regex_str, REG_EXTENDED);
if (result < 0) {
giterr_set_regex(&regex, result);
regfree(&regex);
return -1;
}
/* and throw the callback only on the variables that
* match the regex */
do {
if (regexec(&regex, var->entry->value, 0, NULL, 0) == 0) {
/* early termination by the user is not an error;
* just break and return successfully */
if (fn(var->entry, data) < 0)
break;
}
var = var->next;
} while (var != NULL);
regfree(&regex);
} else {
/* no regex; go through all the variables */
do {
/* early termination by the user is not an error;
* just break and return successfully */
if (fn(var->entry, data) < 0)
break;
var = var->next;
} while (var != NULL);
}
return 0;
}
static int config_set_multivar( static int config_set_multivar(
git_config_backend *cfg, const char *name, const char *regexp, const char *value) git_config_backend *cfg, const char *name, const char *regexp, const char *value)
{ {
@ -477,7 +390,7 @@ static int config_set_multivar(
assert(regexp); assert(regexp);
if ((result = normalize_name(name, &key)) < 0) if ((result = git_config__normalize_name(name, &key)) < 0)
return result; return result;
pos = git_strmap_lookup_index(b->values, key); pos = git_strmap_lookup_index(b->values, key);
@ -550,7 +463,7 @@ static int config_delete(git_config_backend *cfg, const char *name)
int result; int result;
khiter_t pos; khiter_t pos;
if ((result = normalize_name(name, &key)) < 0) if ((result = git_config__normalize_name(name, &key)) < 0)
return result; return result;
pos = git_strmap_lookup_index(b->values, key); pos = git_strmap_lookup_index(b->values, key);
@ -590,11 +503,10 @@ int git_config_file__ondisk(git_config_backend **out, const char *path)
backend->parent.open = config_open; backend->parent.open = config_open;
backend->parent.get = config_get; backend->parent.get = config_get;
backend->parent.get_multivar = config_get_multivar;
backend->parent.set = config_set; backend->parent.set = config_set;
backend->parent.set_multivar = config_set_multivar; backend->parent.set_multivar = config_set_multivar;
backend->parent.del = config_delete; backend->parent.del = config_delete;
backend->parent.foreach = file_foreach; backend->parent.iterator = config_iterator_new;
backend->parent.refresh = config_refresh; backend->parent.refresh = config_refresh;
backend->parent.free = backend_free; backend->parent.free = backend_free;
@ -792,6 +704,11 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
} }
switch (c) { switch (c) {
case 0:
set_parse_error(cfg, 0, "Unexpected end-of-line in section header");
git_buf_free(&buf);
return -1;
case '"': case '"':
++quote_marks; ++quote_marks;
continue; continue;
@ -801,6 +718,12 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
switch (c) { switch (c) {
case '"': case '"':
if (&line[rpos-1] == last_quote) {
set_parse_error(cfg, 0, "Missing closing quotation mark in section header");
git_buf_free(&buf);
return -1;
}
case '\\': case '\\':
break; break;
@ -1293,6 +1216,9 @@ static char *escape_value(const char *ptr)
assert(ptr); assert(ptr);
len = strlen(ptr); len = strlen(ptr);
if (!len)
return git__calloc(1, sizeof(char));
git_buf_grow(&buf, len); git_buf_grow(&buf, len);
while (*ptr != '\0') { while (*ptr != '\0') {
@ -1395,7 +1321,7 @@ static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int i
* standard, this character **has** to be last one in the buf, with * standard, this character **has** to be last one in the buf, with
* no whitespace after it */ * no whitespace after it */
assert(is_multiline_var(value->ptr)); assert(is_multiline_var(value->ptr));
git_buf_truncate(value, git_buf_len(value) - 1); git_buf_shorten(value, 1);
proc_line = fixup_line(line, in_quotes); proc_line = fixup_line(line, in_quotes);
if (proc_line == NULL) { if (proc_line == NULL) {

View File

@ -42,7 +42,7 @@ GIT_INLINE(int) git_config_file_foreach(
int (*fn)(const git_config_entry *entry, void *data), int (*fn)(const git_config_entry *entry, void *data),
void *data) void *data)
{ {
return cfg->foreach(cfg, NULL, fn, data); return git_config_backend_foreach_match(cfg, NULL, fn, data);
} }
GIT_INLINE(int) git_config_file_foreach_match( GIT_INLINE(int) git_config_file_foreach_match(
@ -51,7 +51,7 @@ GIT_INLINE(int) git_config_file_foreach_match(
int (*fn)(const git_config_entry *entry, void *data), int (*fn)(const git_config_entry *entry, void *data),
void *data) void *data)
{ {
return cfg->foreach(cfg, regexp, fn, data); return git_config_backend_foreach_match(cfg, regexp, fn, data);
} }
extern int git_config_file_normalize_section(char *start, char *end); extern int git_config_file_normalize_section(char *start, char *end);

View File

@ -823,15 +823,13 @@ static void pending_number(struct tm *tm, int *num)
} }
static git_time_t approxidate_str(const char *date, static git_time_t approxidate_str(const char *date,
const struct timeval *tv, time_t time_sec,
int *error_ret) int *error_ret)
{ {
int number = 0; int number = 0;
int touched = 0; int touched = 0;
struct tm tm = {0}, now; struct tm tm = {0}, now;
time_t time_sec;
time_sec = tv->tv_sec;
p_localtime_r(&time_sec, &tm); p_localtime_r(&time_sec, &tm);
now = tm; now = tm;
@ -861,7 +859,7 @@ static git_time_t approxidate_str(const char *date,
int git__date_parse(git_time_t *out, const char *date) int git__date_parse(git_time_t *out, const char *date)
{ {
struct timeval tv; time_t time_sec;
git_time_t timestamp; git_time_t timestamp;
int offset, error_ret=0; int offset, error_ret=0;
@ -870,7 +868,9 @@ int git__date_parse(git_time_t *out, const char *date)
return 0; return 0;
} }
p_gettimeofday(&tv, NULL); if (time(&time_sec) == -1)
*out = approxidate_str(date, &tv, &error_ret); return -1;
*out = approxidate_str(date, time_sec, &error_ret);
return error_ret; return error_ret;
} }

View File

@ -13,6 +13,7 @@
#include "pathspec.h" #include "pathspec.h"
#include "index.h" #include "index.h"
#include "odb.h" #include "odb.h"
#include "submodule.h"
#define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0) #define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0)
#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0) #define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0)
@ -77,15 +78,11 @@ static int diff_delta__from_one(
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED)) DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED))
return 0; return 0;
if (entry->mode == GIT_FILEMODE_COMMIT && if (!git_pathspec__match(
DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
return 0;
if (!git_pathspec_match_path(
&diff->pathspec, entry->path, &diff->pathspec, entry->path,
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE), DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE),
&matched_pathspec)) &matched_pathspec, NULL))
return 0; return 0;
delta = diff_delta__alloc(diff, status, entry->path); delta = diff_delta__alloc(diff, status, entry->path);
@ -134,16 +131,12 @@ static int diff_delta__from_two(
{ {
git_diff_delta *delta; git_diff_delta *delta;
int notify_res; int notify_res;
const char *canonical_path = old_entry->path;
if (status == GIT_DELTA_UNMODIFIED && if (status == GIT_DELTA_UNMODIFIED &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
return 0; return 0;
if (old_entry->mode == GIT_FILEMODE_COMMIT &&
new_entry->mode == GIT_FILEMODE_COMMIT &&
DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
return 0;
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
uint32_t temp_mode = old_mode; uint32_t temp_mode = old_mode;
const git_index_entry *temp_entry = old_entry; const git_index_entry *temp_entry = old_entry;
@ -153,7 +146,7 @@ static int diff_delta__from_two(
new_mode = temp_mode; new_mode = temp_mode;
} }
delta = diff_delta__alloc(diff, status, old_entry->path); delta = diff_delta__alloc(diff, status, canonical_path);
GITERR_CHECK_ALLOC(delta); GITERR_CHECK_ALLOC(delta);
git_oid_cpy(&delta->old_file.oid, &old_entry->oid); git_oid_cpy(&delta->old_file.oid, &old_entry->oid);
@ -246,6 +239,11 @@ GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
return str; return str;
} }
const char *git_diff_delta__path(const git_diff_delta *delta)
{
return diff_delta__path(delta);
}
int git_diff_delta__cmp(const void *a, const void *b) int git_diff_delta__cmp(const void *a, const void *b)
{ {
const git_diff_delta *da = a, *db = b; const git_diff_delta *da = a, *db = b;
@ -253,6 +251,33 @@ int git_diff_delta__cmp(const void *a, const void *b)
return val ? val : ((int)da->status - (int)db->status); return val ? val : ((int)da->status - (int)db->status);
} }
int git_diff_delta__casecmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
return val ? val : ((int)da->status - (int)db->status);
}
GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta)
{
return delta->old_file.path ?
delta->old_file.path : delta->new_file.path;
}
int git_diff_delta__i2w_cmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
return val ? val : ((int)da->status - (int)db->status);
}
int git_diff_delta__i2w_casecmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
return val ? val : ((int)da->status - (int)db->status);
}
bool git_diff_delta__should_skip( bool git_diff_delta__should_skip(
const git_diff_options *opts, const git_diff_delta *delta) const git_diff_options *opts, const git_diff_delta *delta)
{ {
@ -356,6 +381,8 @@ static git_diff_list *diff_list_alloc(
diff->strncomp = git__strncasecmp; diff->strncomp = git__strncasecmp;
diff->pfxcomp = git__prefixcmp_icase; diff->pfxcomp = git__prefixcmp_icase;
diff->entrycomp = git_index_entry__cmp_icase; diff->entrycomp = git_index_entry__cmp_icase;
git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp);
} }
return diff; return diff;
@ -377,7 +404,7 @@ static int diff_list_apply_options(
DIFF_FLAG_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE, icase); DIFF_FLAG_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE, icase);
/* initialize pathspec from options */ /* initialize pathspec from options */
if (git_pathspec_init(&diff->pathspec, &opts->pathspec, pool) < 0) if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0)
return -1; return -1;
} }
@ -415,8 +442,18 @@ static int diff_list_apply_options(
if (!opts) { if (!opts) {
diff->opts.context_lines = config_int(cfg, "diff.context", 3); diff->opts.context_lines = config_int(cfg, "diff.context", 3);
if (config_bool(cfg, "diff.ignoreSubmodules", 0)) /* add other defaults here */
diff->opts.flags |= GIT_DIFF_IGNORE_SUBMODULES; }
/* if ignore_submodules not explicitly set, check diff config */
if (diff->opts.ignore_submodules <= 0) {
const char *str;
if (git_config_get_string(&str , cfg, "diff.ignoreSubmodules") < 0)
giterr_clear();
else if (str != NULL &&
git_submodule_parse_ignore(&diff->opts.ignore_submodules, str) < 0)
giterr_clear();
} }
/* if either prefix is not set, figure out appropriate value */ /* if either prefix is not set, figure out appropriate value */
@ -463,7 +500,7 @@ static void diff_list_free(git_diff_list *diff)
} }
git_vector_free(&diff->deltas); git_vector_free(&diff->deltas);
git_pathspec_free(&diff->pathspec); git_pathspec__vfree(&diff->pathspec);
git_pool_clear(&diff->pool); git_pool_clear(&diff->pool);
git__memzero(diff, sizeof(*diff)); git__memzero(diff, sizeof(*diff));
@ -580,35 +617,44 @@ static int maybe_modified_submodule(
int error = 0; int error = 0;
git_submodule *sub; git_submodule *sub;
unsigned int sm_status = 0; unsigned int sm_status = 0;
const git_oid *sm_oid; git_submodule_ignore_t ign = diff->opts.ignore_submodules;
*status = GIT_DELTA_UNMODIFIED; *status = GIT_DELTA_UNMODIFIED;
if (!DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) && if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) ||
!(error = git_submodule_lookup( ign == GIT_SUBMODULE_IGNORE_ALL)
&sub, diff->repo, info->nitem->path)) && return 0;
git_submodule_ignore(sub) != GIT_SUBMODULE_IGNORE_ALL &&
!(error = git_submodule_status(&sm_status, sub))) if ((error = git_submodule_lookup(
{ &sub, diff->repo, info->nitem->path)) < 0) {
/* GIT_EEXISTS means dir with .git in it was found - ignore it */
if (error == GIT_EEXISTS) {
giterr_clear();
error = 0;
}
return error;
}
if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
return 0;
if ((error = git_submodule__status(
&sm_status, NULL, NULL, found_oid, sub, ign)) < 0)
return error;
/* check IS_WD_UNMODIFIED because this case is only used /* check IS_WD_UNMODIFIED because this case is only used
* when the new side of the diff is the working directory * when the new side of the diff is the working directory
*/ */
if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)) if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status))
*status = GIT_DELTA_MODIFIED; *status = GIT_DELTA_MODIFIED;
/* grab OID while we are here */ /* now that we have a HEAD OID, check if HEAD moved */
if (git_oid_iszero(&info->nitem->oid) && if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 &&
(sm_oid = git_submodule_wd_id(sub)) != NULL) !git_oid_equal(&info->oitem->oid, found_oid))
git_oid_cpy(found_oid, sm_oid); *status = GIT_DELTA_MODIFIED;
}
/* GIT_EEXISTS means a dir with .git in it was found - ignore it */ return 0;
if (error == GIT_EEXISTS) {
giterr_clear();
error = 0;
}
return error;
} }
static int maybe_modified( static int maybe_modified(
@ -624,11 +670,11 @@ static int maybe_modified(
bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR); bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);
const char *matched_pathspec; const char *matched_pathspec;
if (!git_pathspec_match_path( if (!git_pathspec__match(
&diff->pathspec, oitem->path, &diff->pathspec, oitem->path,
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE), DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE),
&matched_pathspec)) &matched_pathspec, NULL))
return 0; return 0;
memset(&noid, 0, sizeof(noid)); memset(&noid, 0, sizeof(noid));
@ -665,8 +711,10 @@ static int maybe_modified(
} }
} }
/* if oids and modes match, then file is unmodified */ /* if oids and modes match (and are valid), then file is unmodified */
else if (git_oid_equal(&oitem->oid, &nitem->oid) && omode == nmode) else if (git_oid_equal(&oitem->oid, &nitem->oid) &&
omode == nmode &&
!git_oid_iszero(&oitem->oid))
status = GIT_DELTA_UNMODIFIED; status = GIT_DELTA_UNMODIFIED;
/* if we have an unknown OID and a workdir iterator, then check some /* if we have an unknown OID and a workdir iterator, then check some
@ -707,7 +755,7 @@ static int maybe_modified(
/* if we got here and decided that the files are modified, but we /* if we got here and decided that the files are modified, but we
* haven't calculated the OID of the new item, then calculate it now * haven't calculated the OID of the new item, then calculate it now
*/ */
if (status != GIT_DELTA_UNMODIFIED && git_oid_iszero(&nitem->oid)) { if (status == GIT_DELTA_MODIFIED && git_oid_iszero(&nitem->oid)) {
if (git_oid_iszero(&noid)) { if (git_oid_iszero(&noid)) {
if (git_diff__oid_for_file(diff->repo, if (git_diff__oid_for_file(diff->repo,
nitem->path, nitem->mode, nitem->file_size, &noid) < 0) nitem->path, nitem->mode, nitem->file_size, &noid) < 0)
@ -774,10 +822,15 @@ static int diff_scan_inside_untracked_dir(
/* need to recurse into non-ignored directories */ /* need to recurse into non-ignored directories */
if (!is_ignored && S_ISDIR(info->nitem->mode)) { if (!is_ignored && S_ISDIR(info->nitem->mode)) {
if ((error = git_iterator_advance_into( error = git_iterator_advance_into(&info->nitem, info->new_iter);
&info->nitem, info->new_iter)) < 0)
break; if (!error)
continue; continue;
else if (error == GIT_ENOTFOUND) {
error = 0;
is_ignored = true; /* treat empty as ignored */
} else
break; /* real error, must stop */
} }
/* found a non-ignored item - treat parent dir as untracked */ /* found a non-ignored item - treat parent dir as untracked */
@ -825,7 +878,7 @@ static int handle_unmatched_new_item(
git_buf_clear(&info->ignore_prefix); git_buf_clear(&info->ignore_prefix);
} }
if (S_ISDIR(nitem->mode)) { if (nitem->mode == GIT_FILEMODE_TREE) {
bool recurse_into_dir = contains_oitem; bool recurse_into_dir = contains_oitem;
/* if not already inside an ignored dir, check if this is ignored */ /* if not already inside an ignored dir, check if this is ignored */
@ -929,6 +982,16 @@ static int handle_unmatched_new_item(
else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR)
delta_type = GIT_DELTA_ADDED; delta_type = GIT_DELTA_ADDED;
else if (nitem->mode == GIT_FILEMODE_COMMIT) {
git_submodule *sm;
/* ignore things that are not actual submodules */
if (git_submodule_lookup(&sm, info->repo, nitem->path) != 0) {
giterr_clear();
delta_type = GIT_DELTA_IGNORED;
}
}
/* Actually create the record for this item if necessary */ /* Actually create the record for this item if necessary */
if ((error = diff_delta__from_one(diff, delta_type, nitem)) < 0) if ((error = diff_delta__from_one(diff, delta_type, nitem)) < 0)
return error; return error;
@ -1119,17 +1182,40 @@ int git_diff_tree_to_index(
const git_diff_options *opts) const git_diff_options *opts)
{ {
int error = 0; int error = 0;
bool reset_index_ignore_case = false;
assert(diff && repo); assert(diff && repo);
if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
return error; return error;
if (index->ignore_case) {
git_index__set_ignore_case(index, false);
reset_index_ignore_case = true;
}
DIFF_FROM_ITERATORS( DIFF_FROM_ITERATORS(
git_iterator_for_tree(&a, old_tree, 0, pfx, pfx), git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
git_iterator_for_index(&b, index, 0, pfx, pfx) git_iterator_for_index(&b, index, 0, pfx, pfx)
); );
if (reset_index_ignore_case) {
git_index__set_ignore_case(index, true);
if (!error) {
git_diff_list *d = *diff;
d->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE;
d->strcomp = git__strcasecmp;
d->strncomp = git__strncasecmp;
d->pfxcomp = git__prefixcmp_icase;
d->entrycomp = git_index_entry__cmp_icase;
git_vector_set_cmp(&d->deltas, git_diff_delta__casecmp);
git_vector_sort(&d->deltas);
}
}
return error; return error;
} }
@ -1195,52 +1281,99 @@ size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type)
return count; return count;
} }
int git_diff_is_sorted_icase(const git_diff_list *diff)
{
return (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0;
}
int git_diff__paired_foreach( int git_diff__paired_foreach(
git_diff_list *idx2head, git_diff_list *head2idx,
git_diff_list *wd2idx, git_diff_list *idx2wd,
int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload), int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload),
void *payload) void *payload)
{ {
int cmp; int cmp;
git_diff_delta *i2h, *w2i; git_diff_delta *h2i, *i2w;
size_t i, j, i_max, j_max; size_t i, j, i_max, j_max;
int (*strcomp)(const char *, const char *); int (*strcomp)(const char *, const char *) = git__strcmp;
bool h2i_icase, i2w_icase, icase_mismatch;
i_max = idx2head ? idx2head->deltas.length : 0; i_max = head2idx ? head2idx->deltas.length : 0;
j_max = wd2idx ? wd2idx->deltas.length : 0; j_max = idx2wd ? idx2wd->deltas.length : 0;
if (!i_max && !j_max)
return 0;
/* Get appropriate strcmp function */ /* At some point, tree-to-index diffs will probably never ignore case,
strcomp = idx2head ? idx2head->strcomp : wd2idx ? wd2idx->strcomp : NULL; * even if that isn't true now. Index-to-workdir diffs may or may not
* ignore case, but the index filename for the idx2wd diff should
/* Assert both iterators use matching ignore-case. If this function ever * still be using the canonical case-preserving name.
* supports merging diffs that are not sorted by the same function, then *
* it will need to spool and sort on one of the results before merging * Therefore the main thing we need to do here is make sure the diffs
* are traversed in a compatible order. To do this, we temporarily
* resort a mismatched diff to get the order correct.
*
* In order to traverse renames in the index->workdir, we need to
* ensure that we compare the index name on both sides, so we
* always sort by the old name in the i2w list.
*/ */
if (idx2head && wd2idx) { h2i_icase = head2idx != NULL &&
assert(idx2head->strcomp == wd2idx->strcomp); (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0;
i2w_icase = idx2wd != NULL &&
(idx2wd->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0;
icase_mismatch =
(head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase);
if (icase_mismatch && h2i_icase) {
git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp);
git_vector_sort(&head2idx->deltas);
}
if (i2w_icase && !icase_mismatch) {
strcomp = git__strcasecmp;
git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_casecmp);
git_vector_sort(&idx2wd->deltas);
} else if (idx2wd != NULL) {
git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_cmp);
git_vector_sort(&idx2wd->deltas);
} }
for (i = 0, j = 0; i < i_max || j < j_max; ) { for (i = 0, j = 0; i < i_max || j < j_max; ) {
i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL; h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL; i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
cmp = !w2i ? -1 : !i2h ? 1 : cmp = !i2w ? -1 : !h2i ? 1 :
strcomp(i2h->old_file.path, w2i->old_file.path); strcomp(h2i->new_file.path, i2w->old_file.path);
if (cmp < 0) { if (cmp < 0) {
if (cb(i2h, NULL, payload)) if (cb(h2i, NULL, payload))
return GIT_EUSER; return GIT_EUSER;
i++; i++;
} else if (cmp > 0) { } else if (cmp > 0) {
if (cb(NULL, w2i, payload)) if (cb(NULL, i2w, payload))
return GIT_EUSER; return GIT_EUSER;
j++; j++;
} else { } else {
if (cb(i2h, w2i, payload)) if (cb(h2i, i2w, payload))
return GIT_EUSER; return GIT_EUSER;
i++; j++; i++; j++;
} }
} }
/* restore case-insensitive delta sort */
if (icase_mismatch && h2i_icase) {
git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp);
git_vector_sort(&head2idx->deltas);
}
/* restore idx2wd sort by new path */
if (idx2wd != NULL) {
git_vector_set_cmp(&idx2wd->deltas,
i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp);
git_vector_sort(&idx2wd->deltas);
}
return 0; return 0;
} }

View File

@ -16,6 +16,7 @@
#include "iterator.h" #include "iterator.h"
#include "repository.h" #include "repository.h"
#include "pool.h" #include "pool.h"
#include "odb.h"
#define DIFF_OLD_PREFIX_DEFAULT "a/" #define DIFF_OLD_PREFIX_DEFAULT "a/"
#define DIFF_NEW_PREFIX_DEFAULT "b/" #define DIFF_NEW_PREFIX_DEFAULT "b/"
@ -74,10 +75,20 @@ extern void git_diff__cleanup_modes(
extern void git_diff_list_addref(git_diff_list *diff); extern void git_diff_list_addref(git_diff_list *diff);
extern int git_diff_delta__cmp(const void *a, const void *b); extern int git_diff_delta__cmp(const void *a, const void *b);
extern int git_diff_delta__casecmp(const void *a, const void *b);
extern const char *git_diff_delta__path(const git_diff_delta *delta);
extern bool git_diff_delta__should_skip( extern bool git_diff_delta__should_skip(
const git_diff_options *opts, const git_diff_delta *delta); const git_diff_options *opts, const git_diff_delta *delta);
extern int git_diff_delta__format_file_header(
git_buf *out,
const git_diff_delta *delta,
const char *oldpfx,
const char *newpfx,
int oid_strlen);
extern int git_diff__oid_for_file( extern int git_diff__oid_for_file(
git_repository *, const char *, uint16_t, git_off_t, git_oid *); git_repository *, const char *, uint16_t, git_off_t, git_oid *);
@ -94,17 +105,44 @@ extern int git_diff__paired_foreach(
int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload), int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
void *payload); void *payload);
int git_diff_find_similar__hashsig_for_file( extern int git_diff_find_similar__hashsig_for_file(
void **out, const git_diff_file *f, const char *path, void *p); void **out, const git_diff_file *f, const char *path, void *p);
int git_diff_find_similar__hashsig_for_buf( extern int git_diff_find_similar__hashsig_for_buf(
void **out, const git_diff_file *f, const char *buf, size_t len, void *p); void **out, const git_diff_file *f, const char *buf, size_t len, void *p);
void git_diff_find_similar__hashsig_free(void *sig, void *payload); extern void git_diff_find_similar__hashsig_free(void *sig, void *payload);
int git_diff_find_similar__calc_similarity( extern int git_diff_find_similar__calc_similarity(
int *score, void *siga, void *sigb, void *payload); int *score, void *siga, void *sigb, void *payload);
/*
* Sometimes a git_diff_file will have a zero size; this attempts to
* fill in the size without loading the blob if possible. If that is
* not possible, then it will return the git_odb_object that had to be
* loaded and the caller can use it or dispose of it as needed.
*/
GIT_INLINE(int) git_diff_file__resolve_zero_size(
git_diff_file *file, git_odb_object **odb_obj, git_repository *repo)
{
int error;
git_odb *odb;
size_t len;
git_otype type;
if ((error = git_repository_odb(&odb, repo)) < 0)
return error;
error = git_odb__read_header_or_object(
odb_obj, &len, &type, odb, &file->oid);
git_odb_free(odb);
if (!error)
file->size = (git_off_t)len;
return error;
}
#endif #endif

View File

@ -187,7 +187,7 @@ static int git_diff_driver_load(
git_buf_truncate(&name, namelen + strlen("diff..")); git_buf_truncate(&name, namelen + strlen("diff.."));
git_buf_put(&name, "xfuncname", strlen("xfuncname")); git_buf_put(&name, "xfuncname", strlen("xfuncname"));
if ((error = git_config_get_multivar( if ((error = git_config_get_multivar_foreach(
cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) { cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) {
if (error != GIT_ENOTFOUND) if (error != GIT_ENOTFOUND)
goto done; goto done;
@ -196,7 +196,7 @@ static int git_diff_driver_load(
git_buf_truncate(&name, namelen + strlen("diff..")); git_buf_truncate(&name, namelen + strlen("diff.."));
git_buf_put(&name, "funcname", strlen("funcname")); git_buf_put(&name, "funcname", strlen("funcname"));
if ((error = git_config_get_multivar( if ((error = git_config_get_multivar_foreach(
cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) { cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) {
if (error != GIT_ENOTFOUND) if (error != GIT_ENOTFOUND)
goto done; goto done;
@ -373,10 +373,11 @@ static long diff_context_find(
!ctxt->match_line(ctxt->driver, ctxt->line.ptr, ctxt->line.size)) !ctxt->match_line(ctxt->driver, ctxt->line.ptr, ctxt->line.size))
return -1; return -1;
git_buf_truncate(&ctxt->line, (size_t)out_size); if (out_size > (long)ctxt->line.size)
git_buf_copy_cstr(out, (size_t)out_size, &ctxt->line); out_size = (long)ctxt->line.size;
memcpy(out, ctxt->line.ptr, (size_t)out_size);
return (long)ctxt->line.size; return out_size;
} }
void git_diff_find_context_init( void git_diff_find_context_init(

View File

@ -18,23 +18,23 @@
static bool diff_file_content_binary_by_size(git_diff_file_content *fc) static bool diff_file_content_binary_by_size(git_diff_file_content *fc)
{ {
/* if we have diff opts, check max_size vs file size */ /* if we have diff opts, check max_size vs file size */
if ((fc->file.flags & DIFF_FLAGS_KNOWN_BINARY) == 0 && if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) == 0 &&
fc->opts_max_size > 0 && fc->opts_max_size > 0 &&
fc->file.size > fc->opts_max_size) fc->file->size > fc->opts_max_size)
fc->file.flags |= GIT_DIFF_FLAG_BINARY; fc->file->flags |= GIT_DIFF_FLAG_BINARY;
return ((fc->file.flags & GIT_DIFF_FLAG_BINARY) != 0); return ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0);
} }
static void diff_file_content_binary_by_content(git_diff_file_content *fc) static void diff_file_content_binary_by_content(git_diff_file_content *fc)
{ {
if ((fc->file.flags & DIFF_FLAGS_KNOWN_BINARY) != 0) if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
return; return;
switch (git_diff_driver_content_is_binary( switch (git_diff_driver_content_is_binary(
fc->driver, fc->map.data, fc->map.len)) { fc->driver, fc->map.data, fc->map.len)) {
case 0: fc->file.flags |= GIT_DIFF_FLAG_NOT_BINARY; break; case 0: fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; break;
case 1: fc->file.flags |= GIT_DIFF_FLAG_BINARY; break; case 1: fc->file->flags |= GIT_DIFF_FLAG_BINARY; break;
default: break; default: break;
} }
} }
@ -48,38 +48,39 @@ static int diff_file_content_init_common(
fc->opts_max_size = opts->max_size ? fc->opts_max_size = opts->max_size ?
opts->max_size : DIFF_MAX_FILESIZE; opts->max_size : DIFF_MAX_FILESIZE;
if (!fc->driver) { if (fc->src == GIT_ITERATOR_TYPE_EMPTY)
if (git_diff_driver_lookup(&fc->driver, fc->repo, "") < 0)
return -1;
fc->src = GIT_ITERATOR_TYPE_TREE; fc->src = GIT_ITERATOR_TYPE_TREE;
}
if (!fc->driver &&
git_diff_driver_lookup(&fc->driver, fc->repo, fc->file->path) < 0)
return -1;
/* give driver a chance to modify options */ /* give driver a chance to modify options */
git_diff_driver_update_options(&fc->opts_flags, fc->driver); git_diff_driver_update_options(&fc->opts_flags, fc->driver);
/* make sure file is conceivable mmap-able */ /* make sure file is conceivable mmap-able */
if ((git_off_t)((size_t)fc->file.size) != fc->file.size) if ((git_off_t)((size_t)fc->file->size) != fc->file->size)
fc->file.flags |= GIT_DIFF_FLAG_BINARY; fc->file->flags |= GIT_DIFF_FLAG_BINARY;
/* check if user is forcing text diff the file */ /* check if user is forcing text diff the file */
else if (fc->opts_flags & GIT_DIFF_FORCE_TEXT) { else if (fc->opts_flags & GIT_DIFF_FORCE_TEXT) {
fc->file.flags &= ~GIT_DIFF_FLAG_BINARY; fc->file->flags &= ~GIT_DIFF_FLAG_BINARY;
fc->file.flags |= GIT_DIFF_FLAG_NOT_BINARY; fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY;
} }
/* check if user is forcing binary diff the file */ /* check if user is forcing binary diff the file */
else if (fc->opts_flags & GIT_DIFF_FORCE_BINARY) { else if (fc->opts_flags & GIT_DIFF_FORCE_BINARY) {
fc->file.flags &= ~GIT_DIFF_FLAG_NOT_BINARY; fc->file->flags &= ~GIT_DIFF_FLAG_NOT_BINARY;
fc->file.flags |= GIT_DIFF_FLAG_BINARY; fc->file->flags |= GIT_DIFF_FLAG_BINARY;
} }
diff_file_content_binary_by_size(fc); diff_file_content_binary_by_size(fc);
if ((fc->file.flags & GIT_DIFF_FLAG__NO_DATA) != 0) { if ((fc->flags & GIT_DIFF_FLAG__NO_DATA) != 0) {
fc->file.flags |= GIT_DIFF_FLAG__LOADED; fc->flags |= GIT_DIFF_FLAG__LOADED;
fc->map.len = 0; fc->map.len = 0;
fc->map.data = ""; fc->map.data = "";
} }
if ((fc->file.flags & GIT_DIFF_FLAG__LOADED) != 0) if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0)
diff_file_content_binary_by_content(fc); diff_file_content_binary_by_content(fc);
return 0; return 0;
@ -92,15 +93,14 @@ int git_diff_file_content__init_from_diff(
bool use_old) bool use_old)
{ {
git_diff_delta *delta = git_vector_get(&diff->deltas, delta_index); git_diff_delta *delta = git_vector_get(&diff->deltas, delta_index);
git_diff_file *file = use_old ? &delta->old_file : &delta->new_file;
bool has_data = true; bool has_data = true;
memset(fc, 0, sizeof(*fc)); memset(fc, 0, sizeof(*fc));
fc->repo = diff->repo; fc->repo = diff->repo;
fc->file = use_old ? &delta->old_file : &delta->new_file;
fc->src = use_old ? diff->old_src : diff->new_src; fc->src = use_old ? diff->old_src : diff->new_src;
memcpy(&fc->file, file, sizeof(fc->file));
if (git_diff_driver_lookup(&fc->driver, fc->repo, file->path) < 0) if (git_diff_driver_lookup(&fc->driver, fc->repo, fc->file->path) < 0)
return -1; return -1;
switch (delta->status) { switch (delta->status) {
@ -122,7 +122,7 @@ int git_diff_file_content__init_from_diff(
} }
if (!has_data) if (!has_data)
fc->file.flags |= GIT_DIFF_FLAG__NO_DATA; fc->flags |= GIT_DIFF_FLAG__NO_DATA;
return diff_file_content_init_common(fc, &diff->opts); return diff_file_content_init_common(fc, &diff->opts);
} }
@ -131,21 +131,24 @@ int git_diff_file_content__init_from_blob(
git_diff_file_content *fc, git_diff_file_content *fc,
git_repository *repo, git_repository *repo,
const git_diff_options *opts, const git_diff_options *opts,
const git_blob *blob) const git_blob *blob,
git_diff_file *as_file)
{ {
memset(fc, 0, sizeof(*fc)); memset(fc, 0, sizeof(*fc));
fc->repo = repo; fc->repo = repo;
fc->file = as_file;
fc->blob = blob; fc->blob = blob;
if (!blob) { if (!blob) {
fc->file.flags |= GIT_DIFF_FLAG__NO_DATA; fc->flags |= GIT_DIFF_FLAG__NO_DATA;
} else { } else {
fc->file.flags |= GIT_DIFF_FLAG__LOADED | GIT_DIFF_FLAG_VALID_OID; fc->flags |= GIT_DIFF_FLAG__LOADED;
fc->file.size = git_blob_rawsize(blob); fc->file->flags |= GIT_DIFF_FLAG_VALID_OID;
fc->file.mode = 0644; fc->file->size = git_blob_rawsize(blob);
git_oid_cpy(&fc->file.oid, git_blob_id(blob)); fc->file->mode = GIT_FILEMODE_BLOB;
git_oid_cpy(&fc->file->oid, git_blob_id(blob));
fc->map.len = (size_t)fc->file.size; fc->map.len = (size_t)fc->file->size;
fc->map.data = (char *)git_blob_rawcontent(blob); fc->map.data = (char *)git_blob_rawcontent(blob);
} }
@ -157,18 +160,21 @@ int git_diff_file_content__init_from_raw(
git_repository *repo, git_repository *repo,
const git_diff_options *opts, const git_diff_options *opts,
const char *buf, const char *buf,
size_t buflen) size_t buflen,
git_diff_file *as_file)
{ {
memset(fc, 0, sizeof(*fc)); memset(fc, 0, sizeof(*fc));
fc->repo = repo; fc->repo = repo;
fc->file = as_file;
if (!buf) { if (!buf) {
fc->file.flags |= GIT_DIFF_FLAG__NO_DATA; fc->flags |= GIT_DIFF_FLAG__NO_DATA;
} else { } else {
fc->file.flags |= GIT_DIFF_FLAG__LOADED | GIT_DIFF_FLAG_VALID_OID; fc->flags |= GIT_DIFF_FLAG__LOADED;
fc->file.size = buflen; fc->file->flags |= GIT_DIFF_FLAG_VALID_OID;
fc->file.mode = 0644; fc->file->size = buflen;
git_odb_hash(&fc->file.oid, buf, buflen, GIT_OBJ_BLOB); fc->file->mode = GIT_FILEMODE_BLOB;
git_odb_hash(&fc->file->oid, buf, buflen, GIT_OBJ_BLOB);
fc->map.len = buflen; fc->map.len = buflen;
fc->map.data = (char *)buf; fc->map.data = (char *)buf;
@ -190,7 +196,7 @@ static int diff_file_content_commit_to_str(
unsigned int sm_status = 0; unsigned int sm_status = 0;
const git_oid *sm_head; const git_oid *sm_head;
if ((error = git_submodule_lookup(&sm, fc->repo, fc->file.path)) < 0 || if ((error = git_submodule_lookup(&sm, fc->repo, fc->file->path)) < 0 ||
(error = git_submodule_status(&sm_status, sm)) < 0) { (error = git_submodule_status(&sm_status, sm)) < 0) {
/* GIT_EEXISTS means a "submodule" that has not been git added */ /* GIT_EEXISTS means a "submodule" that has not been git added */
if (error == GIT_EEXISTS) if (error == GIT_EEXISTS)
@ -199,25 +205,25 @@ static int diff_file_content_commit_to_str(
} }
/* update OID if we didn't have it previously */ /* update OID if we didn't have it previously */
if ((fc->file.flags & GIT_DIFF_FLAG_VALID_OID) == 0 && if ((fc->file->flags & GIT_DIFF_FLAG_VALID_OID) == 0 &&
((sm_head = git_submodule_wd_id(sm)) != NULL || ((sm_head = git_submodule_wd_id(sm)) != NULL ||
(sm_head = git_submodule_head_id(sm)) != NULL)) (sm_head = git_submodule_head_id(sm)) != NULL))
{ {
git_oid_cpy(&fc->file.oid, sm_head); git_oid_cpy(&fc->file->oid, sm_head);
fc->file.flags |= GIT_DIFF_FLAG_VALID_OID; fc->file->flags |= GIT_DIFF_FLAG_VALID_OID;
} }
if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status)) if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))
status = "-dirty"; status = "-dirty";
} }
git_oid_tostr(oid, sizeof(oid), &fc->file.oid); git_oid_tostr(oid, sizeof(oid), &fc->file->oid);
if (git_buf_printf(&content, "Subproject commit %s%s\n", oid, status) < 0) if (git_buf_printf(&content, "Subproject commit %s%s\n", oid, status) < 0)
return -1; return -1;
fc->map.len = git_buf_len(&content); fc->map.len = git_buf_len(&content);
fc->map.data = git_buf_detach(&content); fc->map.data = git_buf_detach(&content);
fc->file.flags |= GIT_DIFF_FLAG__FREE_DATA; fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
return 0; return 0;
} }
@ -227,27 +233,17 @@ static int diff_file_content_load_blob(git_diff_file_content *fc)
int error = 0; int error = 0;
git_odb_object *odb_obj = NULL; git_odb_object *odb_obj = NULL;
if (git_oid_iszero(&fc->file.oid)) if (git_oid_iszero(&fc->file->oid))
return 0; return 0;
if (fc->file.mode == GIT_FILEMODE_COMMIT) if (fc->file->mode == GIT_FILEMODE_COMMIT)
return diff_file_content_commit_to_str(fc, false); return diff_file_content_commit_to_str(fc, false);
/* if we don't know size, try to peek at object header first */ /* if we don't know size, try to peek at object header first */
if (!fc->file.size) { if (!fc->file->size) {
git_odb *odb; if ((error = git_diff_file__resolve_zero_size(
size_t len; fc->file, &odb_obj, fc->repo)) < 0)
git_otype type;
if (!(error = git_repository_odb__weakptr(&odb, fc->repo))) {
error = git_odb__read_header_or_object(
&odb_obj, &len, &type, odb, &fc->file.oid);
git_odb_free(odb);
}
if (error)
return error; return error;
fc->file.size = len;
} }
if (diff_file_content_binary_by_size(fc)) if (diff_file_content_binary_by_size(fc))
@ -259,11 +255,11 @@ static int diff_file_content_load_blob(git_diff_file_content *fc)
git_odb_object_free(odb_obj); git_odb_object_free(odb_obj);
} else { } else {
error = git_blob_lookup( error = git_blob_lookup(
(git_blob **)&fc->blob, fc->repo, &fc->file.oid); (git_blob **)&fc->blob, fc->repo, &fc->file->oid);
} }
if (!error) { if (!error) {
fc->file.flags |= GIT_DIFF_FLAG__FREE_BLOB; fc->flags |= GIT_DIFF_FLAG__FREE_BLOB;
fc->map.data = (void *)git_blob_rawcontent(fc->blob); fc->map.data = (void *)git_blob_rawcontent(fc->blob);
fc->map.len = (size_t)git_blob_rawsize(fc->blob); fc->map.len = (size_t)git_blob_rawsize(fc->blob);
} }
@ -279,16 +275,16 @@ static int diff_file_content_load_workdir_symlink(
/* link path on disk could be UTF-16, so prepare a buffer that is /* link path on disk could be UTF-16, so prepare a buffer that is
* big enough to handle some UTF-8 data expansion * big enough to handle some UTF-8 data expansion
*/ */
alloc_len = (ssize_t)(fc->file.size * 2) + 1; alloc_len = (ssize_t)(fc->file->size * 2) + 1;
fc->map.data = git__calloc(alloc_len, sizeof(char)); fc->map.data = git__calloc(alloc_len, sizeof(char));
GITERR_CHECK_ALLOC(fc->map.data); GITERR_CHECK_ALLOC(fc->map.data);
fc->file.flags |= GIT_DIFF_FLAG__FREE_DATA; fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
read_len = p_readlink(git_buf_cstr(path), fc->map.data, alloc_len); read_len = p_readlink(git_buf_cstr(path), fc->map.data, alloc_len);
if (read_len < 0) { if (read_len < 0) {
giterr_set(GITERR_OS, "Failed to read symlink '%s'", fc->file.path); giterr_set(GITERR_OS, "Failed to read symlink '%s'", fc->file->path);
return -1; return -1;
} }
@ -307,28 +303,28 @@ static int diff_file_content_load_workdir_file(
if (fd < 0) if (fd < 0)
return fd; return fd;
if (!fc->file.size && if (!fc->file->size &&
!(fc->file.size = git_futils_filesize(fd))) !(fc->file->size = git_futils_filesize(fd)))
goto cleanup; goto cleanup;
if (diff_file_content_binary_by_size(fc)) if (diff_file_content_binary_by_size(fc))
goto cleanup; goto cleanup;
if ((error = git_filters_load( if ((error = git_filters_load(
&filters, fc->repo, fc->file.path, GIT_FILTER_TO_ODB)) < 0) &filters, fc->repo, fc->file->path, GIT_FILTER_TO_ODB)) < 0)
goto cleanup; goto cleanup;
/* error >= is a filter count */ /* error >= is a filter count */
if (error == 0) { if (error == 0) {
if (!(error = git_futils_mmap_ro( 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->file.flags |= GIT_DIFF_FLAG__UNMAP_DATA; fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA;
else /* fall through to try readbuffer below */ else /* fall through to try readbuffer below */
giterr_clear(); giterr_clear();
} }
if (error != 0) { if (error != 0) {
error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file.size); error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size);
if (error < 0) if (error < 0)
goto cleanup; goto cleanup;
@ -340,7 +336,7 @@ static int diff_file_content_load_workdir_file(
if (!error) { if (!error) {
fc->map.len = git_buf_len(&filtered); fc->map.len = git_buf_len(&filtered);
fc->map.data = git_buf_detach(&filtered); fc->map.data = git_buf_detach(&filtered);
fc->file.flags |= GIT_DIFF_FLAG__FREE_DATA; fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
} }
git_buf_free(&raw); git_buf_free(&raw);
@ -359,26 +355,26 @@ static int diff_file_content_load_workdir(git_diff_file_content *fc)
int error = 0; int error = 0;
git_buf path = GIT_BUF_INIT; git_buf path = GIT_BUF_INIT;
if (fc->file.mode == GIT_FILEMODE_COMMIT) if (fc->file->mode == GIT_FILEMODE_COMMIT)
return diff_file_content_commit_to_str(fc, true); return diff_file_content_commit_to_str(fc, true);
if (fc->file.mode == GIT_FILEMODE_TREE) if (fc->file->mode == GIT_FILEMODE_TREE)
return 0; return 0;
if (git_buf_joinpath( if (git_buf_joinpath(
&path, git_repository_workdir(fc->repo), fc->file.path) < 0) &path, git_repository_workdir(fc->repo), fc->file->path) < 0)
return -1; return -1;
if (S_ISLNK(fc->file.mode)) if (S_ISLNK(fc->file->mode))
error = diff_file_content_load_workdir_symlink(fc, &path); error = diff_file_content_load_workdir_symlink(fc, &path);
else else
error = diff_file_content_load_workdir_file(fc, &path); error = diff_file_content_load_workdir_file(fc, &path);
/* once data is loaded, update OID if we didn't have it previously */ /* once data is loaded, update OID if we didn't have it previously */
if (!error && (fc->file.flags & GIT_DIFF_FLAG_VALID_OID) == 0) { if (!error && (fc->file->flags & GIT_DIFF_FLAG_VALID_OID) == 0) {
error = git_odb_hash( error = git_odb_hash(
&fc->file.oid, fc->map.data, fc->map.len, GIT_OBJ_BLOB); &fc->file->oid, fc->map.data, fc->map.len, GIT_OBJ_BLOB);
fc->file.flags |= GIT_DIFF_FLAG_VALID_OID; fc->file->flags |= GIT_DIFF_FLAG_VALID_OID;
} }
git_buf_free(&path); git_buf_free(&path);
@ -389,10 +385,10 @@ int git_diff_file_content__load(git_diff_file_content *fc)
{ {
int error = 0; int error = 0;
if ((fc->file.flags & GIT_DIFF_FLAG__LOADED) != 0) if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0)
return 0; return 0;
if (fc->file.flags & GIT_DIFF_FLAG_BINARY) if ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0)
return 0; return 0;
if (fc->src == GIT_ITERATOR_TYPE_WORKDIR) if (fc->src == GIT_ITERATOR_TYPE_WORKDIR)
@ -402,7 +398,7 @@ int git_diff_file_content__load(git_diff_file_content *fc)
if (error) if (error)
return error; return error;
fc->file.flags |= GIT_DIFF_FLAG__LOADED; fc->flags |= GIT_DIFF_FLAG__LOADED;
diff_file_content_binary_by_content(fc); diff_file_content_binary_by_content(fc);
@ -411,26 +407,29 @@ int git_diff_file_content__load(git_diff_file_content *fc)
void git_diff_file_content__unload(git_diff_file_content *fc) void git_diff_file_content__unload(git_diff_file_content *fc)
{ {
if (fc->file.flags & GIT_DIFF_FLAG__FREE_DATA) { if ((fc->flags & GIT_DIFF_FLAG__LOADED) == 0)
return;
if (fc->flags & GIT_DIFF_FLAG__FREE_DATA) {
git__free(fc->map.data); git__free(fc->map.data);
fc->map.data = ""; fc->map.data = "";
fc->map.len = 0; fc->map.len = 0;
fc->file.flags &= ~GIT_DIFF_FLAG__FREE_DATA; fc->flags &= ~GIT_DIFF_FLAG__FREE_DATA;
} }
else if (fc->file.flags & GIT_DIFF_FLAG__UNMAP_DATA) { else if (fc->flags & GIT_DIFF_FLAG__UNMAP_DATA) {
git_futils_mmap_free(&fc->map); git_futils_mmap_free(&fc->map);
fc->map.data = ""; fc->map.data = "";
fc->map.len = 0; fc->map.len = 0;
fc->file.flags &= ~GIT_DIFF_FLAG__UNMAP_DATA; fc->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA;
} }
if (fc->file.flags & GIT_DIFF_FLAG__FREE_BLOB) { if (fc->flags & GIT_DIFF_FLAG__FREE_BLOB) {
git_blob_free((git_blob *)fc->blob); git_blob_free((git_blob *)fc->blob);
fc->blob = NULL; fc->blob = NULL;
fc->file.flags &= ~GIT_DIFF_FLAG__FREE_BLOB; fc->flags &= ~GIT_DIFF_FLAG__FREE_BLOB;
} }
fc->file.flags &= ~GIT_DIFF_FLAG__LOADED; fc->flags &= ~GIT_DIFF_FLAG__LOADED;
} }
void git_diff_file_content__clear(git_diff_file_content *fc) void git_diff_file_content__clear(git_diff_file_content *fc)

View File

@ -15,8 +15,9 @@
/* expanded information for one side of a delta */ /* expanded information for one side of a delta */
typedef struct { typedef struct {
git_repository *repo; git_repository *repo;
git_diff_file file; git_diff_file *file;
git_diff_driver *driver; git_diff_driver *driver;
uint32_t flags;
uint32_t opts_flags; uint32_t opts_flags;
git_off_t opts_max_size; git_off_t opts_max_size;
git_iterator_type_t src; git_iterator_type_t src;
@ -34,14 +35,16 @@ extern int git_diff_file_content__init_from_blob(
git_diff_file_content *fc, git_diff_file_content *fc,
git_repository *repo, git_repository *repo,
const git_diff_options *opts, const git_diff_options *opts,
const git_blob *blob); const git_blob *blob,
git_diff_file *as_file);
extern int git_diff_file_content__init_from_raw( extern int git_diff_file_content__init_from_raw(
git_diff_file_content *fc, git_diff_file_content *fc,
git_repository *repo, git_repository *repo,
const git_diff_options *opts, const git_diff_options *opts,
const char *buf, const char *buf,
size_t buflen); size_t buflen,
git_diff_file *as_file);
/* this loads the blob/file-on-disk as needed */ /* this loads the blob/file-on-disk as needed */
extern int git_diff_file_content__load(git_diff_file_content *fc); extern int git_diff_file_content__load(git_diff_file_content *fc);

View File

@ -10,6 +10,7 @@
#include "diff_driver.h" #include "diff_driver.h"
#include "diff_patch.h" #include "diff_patch.h"
#include "diff_xdiff.h" #include "diff_xdiff.h"
#include "fileops.h"
/* cached information about a single span in a diff */ /* cached information about a single span in a diff */
typedef struct diff_patch_line diff_patch_line; typedef struct diff_patch_line diff_patch_line;
@ -41,7 +42,7 @@ struct git_diff_patch {
git_array_t(diff_patch_hunk) hunks; git_array_t(diff_patch_hunk) hunks;
git_array_t(diff_patch_line) lines; git_array_t(diff_patch_line) lines;
size_t oldno, newno; size_t oldno, newno;
size_t content_size; size_t content_size, context_size, header_size;
git_pool flattened; git_pool flattened;
}; };
@ -64,12 +65,12 @@ static void diff_patch_update_binary(git_diff_patch *patch)
if ((patch->delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) if ((patch->delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
return; return;
if ((patch->ofile.file.flags & GIT_DIFF_FLAG_BINARY) != 0 || if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 ||
(patch->nfile.file.flags & GIT_DIFF_FLAG_BINARY) != 0) (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
patch->delta->flags |= GIT_DIFF_FLAG_BINARY; patch->delta->flags |= GIT_DIFF_FLAG_BINARY;
else if ((patch->ofile.file.flags & DIFF_FLAGS_NOT_BINARY) != 0 && else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
(patch->nfile.file.flags & DIFF_FLAGS_NOT_BINARY) != 0) (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
} }
@ -143,42 +144,46 @@ static int diff_patch_load(git_diff_patch *patch, git_diff_output *output)
output && !output->hunk_cb && !output->data_cb) output && !output->hunk_cb && !output->data_cb)
return 0; return 0;
#define DIFF_FLAGS_KNOWN_DATA (GIT_DIFF_FLAG__NO_DATA|GIT_DIFF_FLAG_VALID_OID)
incomplete_data = incomplete_data =
((patch->ofile.file.flags & DIFF_FLAGS_KNOWN_DATA) != 0 && (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
(patch->nfile.file.flags & DIFF_FLAGS_KNOWN_DATA) != 0); (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_OID) != 0) &&
((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
(patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_OID) != 0));
/* always try to load workdir content first because filtering may /* always try to load workdir content first because filtering may
* need 2x data size and this minimizes peak memory footprint * need 2x data size and this minimizes peak memory footprint
*/ */
if (patch->ofile.src == GIT_ITERATOR_TYPE_WORKDIR) { if (patch->ofile.src == GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load(&patch->ofile)) < 0 || if ((error = git_diff_file_content__load(&patch->ofile)) < 0 ||
(patch->ofile.file.flags & GIT_DIFF_FLAG_BINARY) != 0) (patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
goto cleanup; goto cleanup;
} }
if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) { if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load(&patch->nfile)) < 0 || if ((error = git_diff_file_content__load(&patch->nfile)) < 0 ||
(patch->nfile.file.flags & GIT_DIFF_FLAG_BINARY) != 0) (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
goto cleanup; goto cleanup;
} }
/* once workdir has been tried, load other data as needed */ /* once workdir has been tried, load other data as needed */
if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) { if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load(&patch->ofile)) < 0 || if ((error = git_diff_file_content__load(&patch->ofile)) < 0 ||
(patch->ofile.file.flags & GIT_DIFF_FLAG_BINARY) != 0) (patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
goto cleanup; goto cleanup;
} }
if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) { if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load(&patch->nfile)) < 0 || if ((error = git_diff_file_content__load(&patch->nfile)) < 0 ||
(patch->nfile.file.flags & GIT_DIFF_FLAG_BINARY) != 0) (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
goto cleanup; goto cleanup;
} }
/* if we were previously missing an oid, reassess UNMODIFIED state */ /* if previously missing an oid, and now that we have it the two sides
* are the same (and not submodules), update MODIFIED -> UNMODIFIED
*/
if (incomplete_data && if (incomplete_data &&
patch->ofile.file.mode == patch->nfile.file.mode && patch->ofile.file->mode == patch->nfile.file->mode &&
git_oid_equal(&patch->ofile.file.oid, &patch->nfile.file.oid)) patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
git_oid_equal(&patch->ofile.file->oid, &patch->nfile.file->oid) &&
patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
patch->delta->status = GIT_DELTA_UNMODIFIED; patch->delta->status = GIT_DELTA_UNMODIFIED;
cleanup: cleanup:
@ -192,7 +197,7 @@ cleanup:
patch->delta->status != GIT_DELTA_UNMODIFIED && patch->delta->status != GIT_DELTA_UNMODIFIED &&
(patch->ofile.map.len || patch->nfile.map.len) && (patch->ofile.map.len || patch->nfile.map.len) &&
(patch->ofile.map.len != patch->nfile.map.len || (patch->ofile.map.len != patch->nfile.map.len ||
!git_oid_equal(&patch->ofile.file.oid, &patch->nfile.file.oid))) !git_oid_equal(&patch->ofile.file->oid, &patch->nfile.file->oid)))
patch->flags |= GIT_DIFF_PATCH_DIFFABLE; patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
patch->flags |= GIT_DIFF_PATCH_LOADED; patch->flags |= GIT_DIFF_PATCH_LOADED;
@ -225,6 +230,10 @@ static int diff_patch_generate(git_diff_patch *patch, git_diff_output *output)
if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0) if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
return 0; return 0;
/* if we are not looking at the hunks and lines, don't do the diff */
if (!output->hunk_cb && !output->data_cb)
return 0;
if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 && if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 &&
(error = diff_patch_load(patch, output)) < 0) (error = diff_patch_load(patch, output)) < 0)
return error; return error;
@ -279,21 +288,22 @@ int git_diff_foreach(
if (diff_required(diff, "git_diff_foreach") < 0) if (diff_required(diff, "git_diff_foreach") < 0)
return -1; return -1;
diff_output_init((git_diff_output *)&xo, diff_output_init(
&diff->opts, file_cb, hunk_cb, data_cb, payload); &xo.output, &diff->opts, file_cb, hunk_cb, data_cb, payload);
git_xdiff_init(&xo, &diff->opts); git_xdiff_init(&xo, &diff->opts);
git_vector_foreach(&diff->deltas, idx, patch.delta) { git_vector_foreach(&diff->deltas, idx, patch.delta) {
/* check flags against patch status */ /* check flags against patch status */
if (git_diff_delta__should_skip(&diff->opts, patch.delta)) if (git_diff_delta__should_skip(&diff->opts, patch.delta))
continue; continue;
if (!(error = diff_patch_init_from_diff(&patch, diff, idx))) { if (!(error = diff_patch_init_from_diff(&patch, diff, idx))) {
error = diff_patch_file_callback(&patch, (git_diff_output *)&xo); error = diff_patch_file_callback(&patch, &xo.output);
if (!error) if (!error)
error = diff_patch_generate(&patch, (git_diff_output *)&xo); error = diff_patch_generate(&patch, &xo.output);
git_diff_patch_free(&patch); git_diff_patch_free(&patch);
} }
@ -310,26 +320,31 @@ int git_diff_foreach(
typedef struct { typedef struct {
git_diff_patch patch; git_diff_patch patch;
git_diff_delta delta; git_diff_delta delta;
char paths[GIT_FLEX_ARRAY];
} diff_patch_with_delta; } diff_patch_with_delta;
static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo) static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo)
{ {
int error = 0; int error = 0;
git_diff_patch *patch = &pd->patch; git_diff_patch *patch = &pd->patch;
bool has_old = ((patch->ofile.file.flags & GIT_DIFF_FLAG__NO_DATA) == 0); bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
bool has_new = ((patch->nfile.file.flags & GIT_DIFF_FLAG__NO_DATA) == 0); bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
pd->delta.status = has_new ? pd->delta.status = has_new ?
(has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
(has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
if (git_oid_equal(&patch->nfile.file.oid, &patch->ofile.file.oid)) if (git_oid_equal(&patch->nfile.file->oid, &patch->ofile.file->oid))
pd->delta.status = GIT_DELTA_UNMODIFIED; pd->delta.status = GIT_DELTA_UNMODIFIED;
patch->delta = &pd->delta; patch->delta = &pd->delta;
diff_patch_init_common(patch); diff_patch_init_common(patch);
if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
!(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED))
return error;
error = diff_patch_file_callback(patch, (git_diff_output *)xo); error = diff_patch_file_callback(patch, (git_diff_output *)xo);
if (!error) if (!error)
@ -345,7 +360,9 @@ static int diff_patch_from_blobs(
diff_patch_with_delta *pd, diff_patch_with_delta *pd,
git_xdiff_output *xo, git_xdiff_output *xo,
const git_blob *old_blob, const git_blob *old_blob,
const char *old_path,
const git_blob *new_blob, const git_blob *new_blob,
const char *new_path,
const git_diff_options *opts) const git_diff_options *opts)
{ {
int error = 0; int error = 0;
@ -355,29 +372,61 @@ static int diff_patch_from_blobs(
GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
pd->patch.delta = &pd->delta;
if (!repo) /* return two NULL items as UNMODIFIED delta */
return 0;
if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
const git_blob *swap = old_blob; const git_blob *tmp_blob;
old_blob = new_blob; const char *tmp_path;
new_blob = swap; tmp_blob = old_blob; old_blob = new_blob; new_blob = tmp_blob;
tmp_path = old_path; old_path = new_path; new_path = tmp_path;
} }
pd->patch.delta = &pd->delta;
pd->delta.old_file.path = old_path;
pd->delta.new_file.path = new_path;
if ((error = git_diff_file_content__init_from_blob( if ((error = git_diff_file_content__init_from_blob(
&pd->patch.ofile, repo, opts, old_blob)) < 0 || &pd->patch.ofile, repo, opts, old_blob, &pd->delta.old_file)) < 0 ||
(error = git_diff_file_content__init_from_blob( (error = git_diff_file_content__init_from_blob(
&pd->patch.nfile, repo, opts, new_blob)) < 0) &pd->patch.nfile, repo, opts, new_blob, &pd->delta.new_file)) < 0)
return error; return error;
return diff_single_generate(pd, xo); return diff_single_generate(pd, xo);
} }
static int diff_patch_with_delta_alloc(
diff_patch_with_delta **out,
const char **old_path,
const char **new_path)
{
diff_patch_with_delta *pd;
size_t old_len = *old_path ? strlen(*old_path) : 0;
size_t new_len = *new_path ? strlen(*new_path) : 0;
*out = pd = git__calloc(1, sizeof(*pd) + old_len + new_len + 2);
GITERR_CHECK_ALLOC(pd);
pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED;
if (*old_path) {
memcpy(&pd->paths[0], *old_path, old_len);
*old_path = &pd->paths[0];
} else if (*new_path)
*old_path = &pd->paths[old_len + 1];
if (*new_path) {
memcpy(&pd->paths[old_len + 1], *new_path, new_len);
*new_path = &pd->paths[old_len + 1];
} else if (*old_path)
*new_path = &pd->paths[0];
return 0;
}
int git_diff_blobs( int git_diff_blobs(
const git_blob *old_blob, const git_blob *old_blob,
const char *old_path,
const git_blob *new_blob, const git_blob *new_blob,
const char *new_path,
const git_diff_options *opts, const git_diff_options *opts,
git_diff_file_cb file_cb, git_diff_file_cb file_cb,
git_diff_hunk_cb hunk_cb, git_diff_hunk_cb hunk_cb,
@ -392,12 +441,18 @@ int git_diff_blobs(
memset(&xo, 0, sizeof(xo)); memset(&xo, 0, sizeof(xo));
diff_output_init( diff_output_init(
(git_diff_output *)&xo, opts, file_cb, hunk_cb, data_cb, payload); &xo.output, opts, file_cb, hunk_cb, data_cb, payload);
git_xdiff_init(&xo, opts); git_xdiff_init(&xo, opts);
error = diff_patch_from_blobs(&pd, &xo, old_blob, new_blob, opts); if (!old_path && new_path)
old_path = new_path;
else if (!new_path && old_path)
new_path = old_path;
git_diff_patch_free((git_diff_patch *)&pd); error = diff_patch_from_blobs(
&pd, &xo, old_blob, old_path, new_blob, new_path, opts);
git_diff_patch_free(&pd.patch);
return error; return error;
} }
@ -405,7 +460,9 @@ int git_diff_blobs(
int git_diff_patch_from_blobs( int git_diff_patch_from_blobs(
git_diff_patch **out, git_diff_patch **out,
const git_blob *old_blob, const git_blob *old_blob,
const char *old_path,
const git_blob *new_blob, const git_blob *new_blob,
const char *new_path,
const git_diff_options *opts) const git_diff_options *opts)
{ {
int error = 0; int error = 0;
@ -415,16 +472,18 @@ int git_diff_patch_from_blobs(
assert(out); assert(out);
*out = NULL; *out = NULL;
pd = git__calloc(1, sizeof(*pd)); if (diff_patch_with_delta_alloc(&pd, &old_path, &new_path) < 0)
GITERR_CHECK_ALLOC(pd); return -1;
pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED;
memset(&xo, 0, sizeof(xo)); memset(&xo, 0, sizeof(xo));
diff_output_to_patch((git_diff_output *)&xo, &pd->patch); diff_output_to_patch(&xo.output, &pd->patch);
git_xdiff_init(&xo, opts); git_xdiff_init(&xo, opts);
if (!(error = diff_patch_from_blobs(pd, &xo, old_blob, new_blob, opts))) error = diff_patch_from_blobs(
pd, &xo, old_blob, old_path, new_blob, new_path, opts);
if (!error)
*out = (git_diff_patch *)pd; *out = (git_diff_patch *)pd;
else else
git_diff_patch_free((git_diff_patch *)pd); git_diff_patch_free((git_diff_patch *)pd);
@ -436,8 +495,10 @@ static int diff_patch_from_blob_and_buffer(
diff_patch_with_delta *pd, diff_patch_with_delta *pd,
git_xdiff_output *xo, git_xdiff_output *xo,
const git_blob *old_blob, const git_blob *old_blob,
const char *old_path,
const char *buf, const char *buf,
size_t buflen, size_t buflen,
const char *buf_path,
const git_diff_options *opts) const git_diff_options *opts)
{ {
int error = 0; int error = 0;
@ -448,28 +509,36 @@ static int diff_patch_from_blob_and_buffer(
pd->patch.delta = &pd->delta; pd->patch.delta = &pd->delta;
if (!repo && !buf) /* return two NULL items as UNMODIFIED delta */
return 0;
if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
pd->delta.old_file.path = buf_path;
pd->delta.new_file.path = old_path;
if (!(error = git_diff_file_content__init_from_raw( if (!(error = git_diff_file_content__init_from_raw(
&pd->patch.ofile, repo, opts, buf, buflen))) &pd->patch.ofile, repo, opts, buf, buflen, &pd->delta.old_file)))
error = git_diff_file_content__init_from_blob( error = git_diff_file_content__init_from_blob(
&pd->patch.nfile, repo, opts, old_blob); &pd->patch.nfile, repo, opts, old_blob, &pd->delta.new_file);
} else { } else {
pd->delta.old_file.path = old_path;
pd->delta.new_file.path = buf_path;
if (!(error = git_diff_file_content__init_from_blob( if (!(error = git_diff_file_content__init_from_blob(
&pd->patch.ofile, repo, opts, old_blob))) &pd->patch.ofile, repo, opts, old_blob, &pd->delta.old_file)))
error = git_diff_file_content__init_from_raw( error = git_diff_file_content__init_from_raw(
&pd->patch.nfile, repo, opts, buf, buflen); &pd->patch.nfile, repo, opts, buf, buflen, &pd->delta.new_file);
} }
if (error < 0)
return error;
return diff_single_generate(pd, xo); return diff_single_generate(pd, xo);
} }
int git_diff_blob_to_buffer( int git_diff_blob_to_buffer(
const git_blob *old_blob, const git_blob *old_blob,
const char *old_path,
const char *buf, const char *buf,
size_t buflen, size_t buflen,
const char *buf_path,
const git_diff_options *opts, const git_diff_options *opts,
git_diff_file_cb file_cb, git_diff_file_cb file_cb,
git_diff_hunk_cb hunk_cb, git_diff_hunk_cb hunk_cb,
@ -484,13 +553,18 @@ int git_diff_blob_to_buffer(
memset(&xo, 0, sizeof(xo)); memset(&xo, 0, sizeof(xo));
diff_output_init( diff_output_init(
(git_diff_output *)&xo, opts, file_cb, hunk_cb, data_cb, payload); &xo.output, opts, file_cb, hunk_cb, data_cb, payload);
git_xdiff_init(&xo, opts); git_xdiff_init(&xo, opts);
error = diff_patch_from_blob_and_buffer( if (!old_path && buf_path)
&pd, &xo, old_blob, buf, buflen, opts); old_path = buf_path;
else if (!buf_path && old_path)
buf_path = old_path;
git_diff_patch_free((git_diff_patch *)&pd); error = diff_patch_from_blob_and_buffer(
&pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts);
git_diff_patch_free(&pd.patch);
return error; return error;
} }
@ -498,8 +572,10 @@ int git_diff_blob_to_buffer(
int git_diff_patch_from_blob_and_buffer( int git_diff_patch_from_blob_and_buffer(
git_diff_patch **out, git_diff_patch **out,
const git_blob *old_blob, const git_blob *old_blob,
const char *old_path,
const char *buf, const char *buf,
size_t buflen, size_t buflen,
const char *buf_path,
const git_diff_options *opts) const git_diff_options *opts)
{ {
int error = 0; int error = 0;
@ -509,17 +585,18 @@ int git_diff_patch_from_blob_and_buffer(
assert(out); assert(out);
*out = NULL; *out = NULL;
pd = git__calloc(1, sizeof(*pd)); if (diff_patch_with_delta_alloc(&pd, &old_path, &buf_path) < 0)
GITERR_CHECK_ALLOC(pd); return -1;
pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED;
memset(&xo, 0, sizeof(xo)); memset(&xo, 0, sizeof(xo));
diff_output_to_patch((git_diff_output *)&xo, &pd->patch); diff_output_to_patch(&xo.output, &pd->patch);
git_xdiff_init(&xo, opts); git_xdiff_init(&xo, opts);
if (!(error = diff_patch_from_blob_and_buffer( error = diff_patch_from_blob_and_buffer(
pd, &xo, old_blob, buf, buflen, opts))) pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts);
if (!error)
*out = (git_diff_patch *)pd; *out = (git_diff_patch *)pd;
else else
git_diff_patch_free((git_diff_patch *)pd); git_diff_patch_free((git_diff_patch *)pd);
@ -565,13 +642,13 @@ int git_diff_get_patch(
if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0) if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0)
return error; return error;
diff_output_to_patch((git_diff_output *)&xo, patch); diff_output_to_patch(&xo.output, patch);
git_xdiff_init(&xo, &diff->opts); git_xdiff_init(&xo, &diff->opts);
error = diff_patch_file_callback(patch, (git_diff_output *)&xo); error = diff_patch_file_callback(patch, &xo.output);
if (!error) if (!error)
error = diff_patch_generate(patch, (git_diff_output *)&xo); error = diff_patch_generate(patch, &xo.output);
if (!error) { if (!error) {
/* if cumulative diff size is < 0.5 total size, flatten the patch */ /* if cumulative diff size is < 0.5 total size, flatten the patch */
@ -733,6 +810,39 @@ notfound:
return diff_error_outofrange(thing); return diff_error_outofrange(thing);
} }
size_t git_diff_patch_size(
git_diff_patch *patch,
int include_context,
int include_hunk_headers,
int include_file_headers)
{
size_t out;
assert(patch);
out = patch->content_size;
if (!include_context)
out -= patch->context_size;
if (include_hunk_headers)
out += patch->header_size;
if (include_file_headers) {
git_buf file_header = GIT_BUF_INIT;
if (git_diff_delta__format_file_header(
&file_header, patch->delta, NULL, NULL, 0) < 0)
giterr_clear();
else
out += git_buf_len(&file_header);
git_buf_free(&file_header);
}
return out;
}
git_diff_list *git_diff_patch__diff(git_diff_patch *patch) git_diff_list *git_diff_patch__diff(git_diff_patch *patch)
{ {
return patch->diff; return patch->diff;
@ -827,6 +937,8 @@ static int diff_patch_hunk_cb(
hunk->header[header_len] = '\0'; hunk->header[header_len] = '\0';
hunk->header_len = header_len; hunk->header_len = header_len;
patch->header_size += header_len;
hunk->line_start = git_array_size(patch->lines); hunk->line_start = git_array_size(patch->lines);
hunk->line_count = 0; hunk->line_count = 0;
@ -847,6 +959,7 @@ static int diff_patch_line_cb(
git_diff_patch *patch = payload; git_diff_patch *patch = payload;
diff_patch_hunk *hunk; diff_patch_hunk *hunk;
diff_patch_line *line; diff_patch_line *line;
const char *content_end = content + content_len;
GIT_UNUSED(delta); GIT_UNUSED(delta);
GIT_UNUSED(range); GIT_UNUSED(range);
@ -861,34 +974,43 @@ static int diff_patch_line_cb(
line->len = content_len; line->len = content_len;
line->origin = line_origin; line->origin = line_origin;
patch->content_size += content_len;
/* do some bookkeeping so we can provide old/new line numbers */ /* do some bookkeeping so we can provide old/new line numbers */
for (line->lines = 0; content_len > 0; --content_len) { line->lines = 0;
while (content < content_end)
if (*content++ == '\n') if (*content++ == '\n')
++line->lines; ++line->lines;
}
patch->content_size += content_len;
switch (line_origin) { switch (line_origin) {
case GIT_DIFF_LINE_ADDITION: case GIT_DIFF_LINE_ADDITION:
patch->content_size += 1;
case GIT_DIFF_LINE_DEL_EOFNL: case GIT_DIFF_LINE_DEL_EOFNL:
line->oldno = -1; line->oldno = -1;
line->newno = patch->newno; line->newno = patch->newno;
patch->newno += line->lines; patch->newno += line->lines;
break; break;
case GIT_DIFF_LINE_DELETION: case GIT_DIFF_LINE_DELETION:
patch->content_size += 1;
case GIT_DIFF_LINE_ADD_EOFNL: case GIT_DIFF_LINE_ADD_EOFNL:
line->oldno = patch->oldno; line->oldno = patch->oldno;
line->newno = -1; line->newno = -1;
patch->oldno += line->lines; patch->oldno += line->lines;
break; break;
default: case GIT_DIFF_LINE_CONTEXT:
patch->content_size += 1;
patch->context_size += 1;
case GIT_DIFF_LINE_CONTEXT_EOFNL:
patch->context_size += content_len;
line->oldno = patch->oldno; line->oldno = patch->oldno;
line->newno = patch->newno; line->newno = patch->newno;
patch->oldno += line->lines; patch->oldno += line->lines;
patch->newno += line->lines; patch->newno += line->lines;
break; break;
default:
assert(false);
break;
} }
hunk->line_count++; hunk->line_count++;

View File

@ -7,7 +7,7 @@
#include "common.h" #include "common.h"
#include "diff.h" #include "diff.h"
#include "diff_patch.h" #include "diff_patch.h"
#include "buffer.h" #include "fileops.h"
typedef struct { typedef struct {
git_diff_list *diff; git_diff_list *diff;
@ -21,14 +21,15 @@ static int diff_print_info_init(
diff_print_info *pi, diff_print_info *pi,
git_buf *out, git_diff_list *diff, git_diff_data_cb cb, void *payload) git_buf *out, git_diff_list *diff, git_diff_data_cb cb, void *payload)
{ {
assert(diff && diff->repo);
pi->diff = diff; pi->diff = diff;
pi->print_cb = cb; pi->print_cb = cb;
pi->payload = payload; pi->payload = payload;
pi->buf = out; pi->buf = out;
if (git_repository__cvar(&pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0) if (!diff || !diff->repo)
pi->oid_strlen = GIT_ABBREV_DEFAULT;
else if (git_repository__cvar(
&pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0)
return -1; return -1;
pi->oid_strlen += 1; /* for NUL byte */ pi->oid_strlen += 1; /* for NUL byte */
@ -41,11 +42,11 @@ static int diff_print_info_init(
return 0; return 0;
} }
static char pick_suffix(int mode) static char diff_pick_suffix(int mode)
{ {
if (S_ISDIR(mode)) if (S_ISDIR(mode))
return '/'; return '/';
else if (mode & 0100) //-V536 else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */
/* in git, modes are very regular, so we must have 0100755 mode */ /* in git, modes are very regular, so we must have 0100755 mode */
return '*'; return '*';
else else
@ -76,45 +77,49 @@ static int callback_error(void)
return GIT_EUSER; return GIT_EUSER;
} }
static int print_compact( static int diff_print_one_compact(
const git_diff_delta *delta, float progress, void *data) const git_diff_delta *delta, float progress, void *data)
{ {
diff_print_info *pi = data; diff_print_info *pi = data;
git_buf *out = pi->buf;
char old_suffix, new_suffix, code = git_diff_status_char(delta->status); char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
int (*strcomp)(const char *, const char *) =
pi->diff ? pi->diff->strcomp : git__strcmp;
GIT_UNUSED(progress); GIT_UNUSED(progress);
if (code == ' ') if (code == ' ')
return 0; return 0;
old_suffix = pick_suffix(delta->old_file.mode); old_suffix = diff_pick_suffix(delta->old_file.mode);
new_suffix = pick_suffix(delta->new_file.mode); new_suffix = diff_pick_suffix(delta->new_file.mode);
git_buf_clear(pi->buf); git_buf_clear(out);
if (delta->old_file.path != delta->new_file.path && if (delta->old_file.path != delta->new_file.path &&
pi->diff->strcomp(delta->old_file.path,delta->new_file.path) != 0) strcomp(delta->old_file.path,delta->new_file.path) != 0)
git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code, git_buf_printf(out, "%c\t%s%c %s%c\n", code,
delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
else if (delta->old_file.mode != delta->new_file.mode && else if (delta->old_file.mode != delta->new_file.mode &&
delta->old_file.mode != 0 && delta->new_file.mode != 0) delta->old_file.mode != 0 && delta->new_file.mode != 0)
git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code, git_buf_printf(out, "%c\t%s%c %s%c\n", code,
delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode); delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
else if (old_suffix != ' ') else if (old_suffix != ' ')
git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);
else else
git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old_file.path); git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path);
if (git_buf_oom(pi->buf)) if (git_buf_oom(out))
return -1; return -1;
if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) git_buf_cstr(out), git_buf_len(out), pi->payload))
return callback_error(); return callback_error();
return 0; return 0;
} }
/* print a git_diff_list to a print callback in compact format */
int git_diff_print_compact( int git_diff_print_compact(
git_diff_list *diff, git_diff_list *diff,
git_diff_data_cb print_cb, git_diff_data_cb print_cb,
@ -125,17 +130,18 @@ int git_diff_print_compact(
diff_print_info pi; diff_print_info pi;
if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
error = git_diff_foreach(diff, print_compact, NULL, NULL, &pi); error = git_diff_foreach(diff, diff_print_one_compact, NULL, NULL, &pi);
git_buf_free(&buf); git_buf_free(&buf);
return error; return error;
} }
static int print_raw( static int diff_print_one_raw(
const git_diff_delta *delta, float progress, void *data) const git_diff_delta *delta, float progress, void *data)
{ {
diff_print_info *pi = data; diff_print_info *pi = data;
git_buf *out = pi->buf;
char code = git_diff_status_char(delta->status); char code = git_diff_status_char(delta->status);
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
@ -144,36 +150,37 @@ static int print_raw(
if (code == ' ') if (code == ' ')
return 0; return 0;
git_buf_clear(pi->buf); git_buf_clear(out);
git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid); git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid);
git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid); git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid);
git_buf_printf( git_buf_printf(
pi->buf, ":%06o %06o %s... %s... %c", out, ":%06o %06o %s... %s... %c",
delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code); delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);
if (delta->similarity > 0) if (delta->similarity > 0)
git_buf_printf(pi->buf, "%03u", delta->similarity); git_buf_printf(out, "%03u", delta->similarity);
if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED) if (delta->old_file.path != delta->new_file.path)
git_buf_printf( git_buf_printf(
pi->buf, "\t%s %s\n", delta->old_file.path, delta->new_file.path); out, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
else else
git_buf_printf( git_buf_printf(
pi->buf, "\t%s\n", delta->old_file.path ? out, "\t%s\n", delta->old_file.path ?
delta->old_file.path : delta->new_file.path); delta->old_file.path : delta->new_file.path);
if (git_buf_oom(pi->buf)) if (git_buf_oom(out))
return -1; return -1;
if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) git_buf_cstr(out), git_buf_len(out), pi->payload))
return callback_error(); return callback_error();
return 0; return 0;
} }
/* print a git_diff_list to a print callback in raw output format */
int git_diff_print_raw( int git_diff_print_raw(
git_diff_list *diff, git_diff_list *diff,
git_diff_data_cb print_cb, git_diff_data_cb print_cb,
@ -184,72 +191,53 @@ int git_diff_print_raw(
diff_print_info pi; diff_print_info pi;
if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
error = git_diff_foreach(diff, print_raw, NULL, NULL, &pi); error = git_diff_foreach(diff, diff_print_one_raw, NULL, NULL, &pi);
git_buf_free(&buf); git_buf_free(&buf);
return error; return error;
} }
static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta) static int diff_print_oid_range(
git_buf *out, const git_diff_delta *delta, int oid_strlen)
{ {
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid); git_oid_tostr(start_oid, oid_strlen, &delta->old_file.oid);
git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid); git_oid_tostr(end_oid, oid_strlen, &delta->new_file.oid);
/* TODO: Match git diff more closely */ /* TODO: Match git diff more closely */
if (delta->old_file.mode == delta->new_file.mode) { if (delta->old_file.mode == delta->new_file.mode) {
git_buf_printf(pi->buf, "index %s..%s %o\n", git_buf_printf(out, "index %s..%s %o\n",
start_oid, end_oid, delta->old_file.mode); start_oid, end_oid, delta->old_file.mode);
} else { } else {
if (delta->old_file.mode == 0) { if (delta->old_file.mode == 0) {
git_buf_printf(pi->buf, "new file mode %o\n", delta->new_file.mode); git_buf_printf(out, "new file mode %o\n", delta->new_file.mode);
} else if (delta->new_file.mode == 0) { } else if (delta->new_file.mode == 0) {
git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old_file.mode); git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
} else { } else {
git_buf_printf(pi->buf, "old mode %o\n", delta->old_file.mode); git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
git_buf_printf(pi->buf, "new mode %o\n", delta->new_file.mode); git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
} }
git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid); git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
} }
if (git_buf_oom(pi->buf)) if (git_buf_oom(out))
return -1; return -1;
return 0; return 0;
} }
static int print_patch_file( static int diff_delta_format_with_paths(
const git_diff_delta *delta, float progress, void *data) git_buf *out,
const git_diff_delta *delta,
const char *oldpfx,
const char *newpfx,
const char *template)
{ {
diff_print_info *pi = data;
const char *oldpfx = pi->diff->opts.old_prefix;
const char *oldpath = delta->old_file.path; const char *oldpath = delta->old_file.path;
const char *newpfx = pi->diff->opts.new_prefix;
const char *newpath = delta->new_file.path; const char *newpath = delta->new_file.path;
GIT_UNUSED(progress);
if (S_ISDIR(delta->new_file.mode) ||
delta->status == GIT_DELTA_UNMODIFIED ||
delta->status == GIT_DELTA_IGNORED ||
(delta->status == GIT_DELTA_UNTRACKED &&
(pi->diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0))
return 0;
if (!oldpfx)
oldpfx = DIFF_OLD_PREFIX_DEFAULT;
if (!newpfx)
newpfx = DIFF_NEW_PREFIX_DEFAULT;
git_buf_clear(pi->buf);
git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
if (print_oid_range(pi, delta) < 0)
return -1;
if (git_oid_iszero(&delta->old_file.oid)) { if (git_oid_iszero(&delta->old_file.oid)) {
oldpfx = ""; oldpfx = "";
oldpath = "/dev/null"; oldpath = "/dev/null";
@ -259,12 +247,59 @@ static int print_patch_file(
newpath = "/dev/null"; newpath = "/dev/null";
} }
if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) { return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath);
git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath);
git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath);
} }
if (git_buf_oom(pi->buf)) int git_diff_delta__format_file_header(
git_buf *out,
const git_diff_delta *delta,
const char *oldpfx,
const char *newpfx,
int oid_strlen)
{
if (!oldpfx)
oldpfx = DIFF_OLD_PREFIX_DEFAULT;
if (!newpfx)
newpfx = DIFF_NEW_PREFIX_DEFAULT;
if (!oid_strlen)
oid_strlen = GIT_ABBREV_DEFAULT + 1;
git_buf_clear(out);
git_buf_printf(out, "diff --git %s%s %s%s\n",
oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
if (diff_print_oid_range(out, delta, oid_strlen) < 0)
return -1;
if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
diff_delta_format_with_paths(
out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n");
return git_buf_oom(out) ? -1 : 0;
}
static int diff_print_patch_file(
const git_diff_delta *delta, float progress, void *data)
{
diff_print_info *pi = data;
const char *oldpfx =
pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT;
const char *newpfx =
pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;
uint32_t opts_flags = pi->diff ? pi->diff->opts.flags : GIT_DIFF_NORMAL;
GIT_UNUSED(progress);
if (S_ISDIR(delta->new_file.mode) ||
delta->status == GIT_DELTA_UNMODIFIED ||
delta->status == GIT_DELTA_IGNORED ||
(delta->status == GIT_DELTA_UNTRACKED &&
(opts_flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0))
return 0;
if (git_diff_delta__format_file_header(
pi->buf, delta, oldpfx, newpfx, pi->oid_strlen) < 0)
return -1; return -1;
if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
@ -275,10 +310,10 @@ static int print_patch_file(
return 0; return 0;
git_buf_clear(pi->buf); git_buf_clear(pi->buf);
git_buf_printf(
pi->buf, "Binary files %s%s and %s%s differ\n", if (diff_delta_format_with_paths(
oldpfx, oldpath, newpfx, newpath); pi->buf, delta, oldpfx, newpfx,
if (git_buf_oom(pi->buf)) "Binary files %s%s and %s%s differ\n") < 0)
return -1; return -1;
if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_BINARY, if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_BINARY,
@ -288,7 +323,7 @@ static int print_patch_file(
return 0; return 0;
} }
static int print_patch_hunk( static int diff_print_patch_hunk(
const git_diff_delta *d, const git_diff_delta *d,
const git_diff_range *r, const git_diff_range *r,
const char *header, const char *header,
@ -311,7 +346,7 @@ static int print_patch_hunk(
return 0; return 0;
} }
static int print_patch_line( static int diff_print_patch_line(
const git_diff_delta *delta, const git_diff_delta *delta,
const git_diff_range *range, const git_diff_range *range,
char line_origin, /* GIT_DIFF_LINE value from above */ char line_origin, /* GIT_DIFF_LINE value from above */
@ -343,6 +378,7 @@ static int print_patch_line(
return 0; return 0;
} }
/* print a git_diff_list to an output callback in patch format */
int git_diff_print_patch( int git_diff_print_patch(
git_diff_list *diff, git_diff_list *diff,
git_diff_data_cb print_cb, git_diff_data_cb print_cb,
@ -354,27 +390,15 @@ int git_diff_print_patch(
if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
error = git_diff_foreach( error = git_diff_foreach(
diff, print_patch_file, print_patch_hunk, print_patch_line, &pi); diff, diff_print_patch_file, diff_print_patch_hunk,
diff_print_patch_line, &pi);
git_buf_free(&buf); git_buf_free(&buf);
return error; return error;
} }
/* print a git_diff_patch to an output callback */
static int print_to_buffer_cb(
const git_diff_delta *delta,
const git_diff_range *range,
char line_origin,
const char *content,
size_t content_len,
void *payload)
{
git_buf *output = payload;
GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin);
return git_buf_put(output, content, content_len);
}
int git_diff_patch_print( int git_diff_patch_print(
git_diff_patch *patch, git_diff_patch *patch,
git_diff_data_cb print_cb, git_diff_data_cb print_cb,
@ -389,13 +413,28 @@ int git_diff_patch_print(
if (!(error = diff_print_info_init( if (!(error = diff_print_info_init(
&pi, &temp, git_diff_patch__diff(patch), print_cb, payload))) &pi, &temp, git_diff_patch__diff(patch), print_cb, payload)))
error = git_diff_patch__invoke_callbacks( error = git_diff_patch__invoke_callbacks(
patch, print_patch_file, print_patch_hunk, print_patch_line, &pi); patch, diff_print_patch_file, diff_print_patch_hunk,
diff_print_patch_line, &pi);
git_buf_free(&temp); git_buf_free(&temp);
return error; return error;
} }
static int diff_print_to_buffer_cb(
const git_diff_delta *delta,
const git_diff_range *range,
char line_origin,
const char *content,
size_t content_len,
void *payload)
{
git_buf *output = payload;
GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin);
return git_buf_put(output, content, content_len);
}
/* print a git_diff_patch to a string buffer */
int git_diff_patch_to_str( int git_diff_patch_to_str(
char **string, char **string,
git_diff_patch *patch) git_diff_patch *patch)
@ -403,7 +442,7 @@ int git_diff_patch_to_str(
int error; int error;
git_buf output = GIT_BUF_INIT; git_buf output = GIT_BUF_INIT;
error = git_diff_patch_print(patch, print_to_buffer_cb, &output); error = git_diff_patch_print(patch, diff_print_to_buffer_cb, &output);
/* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1, /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1,
* meaning a memory allocation failure, so just map to -1... * meaning a memory allocation failure, so just map to -1...

View File

@ -408,57 +408,99 @@ GIT_INLINE(git_diff_file *) similarity_get_file(git_diff_list *diff, size_t idx)
return (idx & 1) ? &delta->new_file : &delta->old_file; return (idx & 1) ? &delta->new_file : &delta->old_file;
} }
static int similarity_calc( typedef struct {
git_diff_list *diff, size_t idx;
git_iterator_type_t src;
git_repository *repo;
git_diff_file *file;
git_buf data;
git_odb_object *odb_obj;
git_blob *blob;
} similarity_info;
static int similarity_init(
similarity_info *info, git_diff_list *diff, size_t file_idx)
{
info->idx = file_idx;
info->src = (file_idx & 1) ? diff->new_src : diff->old_src;
info->repo = diff->repo;
info->file = similarity_get_file(diff, file_idx);
info->odb_obj = NULL;
info->blob = NULL;
git_buf_init(&info->data, 0);
if (info->file->size > 0)
return 0;
return git_diff_file__resolve_zero_size(
info->file, &info->odb_obj, info->repo);
}
static int similarity_sig(
similarity_info *info,
const git_diff_find_options *opts, const git_diff_find_options *opts,
size_t file_idx,
void **cache) void **cache)
{ {
int error = 0; int error = 0;
git_diff_file *file = similarity_get_file(diff, file_idx); git_diff_file *file = info->file;
git_iterator_type_t src = (file_idx & 1) ? diff->new_src : diff->old_src;
if (src == GIT_ITERATOR_TYPE_WORKDIR) { /* compute hashsig from file */
git_buf path = GIT_BUF_INIT;
/* TODO: apply wd-to-odb filters to file data if necessary */
if (info->src == GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_buf_joinpath( if ((error = git_buf_joinpath(
&path, git_repository_workdir(diff->repo), file->path)) < 0) &info->data, git_repository_workdir(info->repo), file->path)) < 0)
return error; return error;
/* if path is not a regular file, just skip this item */ /* if path is not a regular file, just skip this item */
if (git_path_isfile(path.ptr)) if (!git_path_isfile(info->data.ptr))
return 0;
/* TODO: apply wd-to-odb filters to file data if necessary */
error = opts->metric->file_signature( error = opts->metric->file_signature(
&cache[file_idx], file, path.ptr, opts->metric->payload); &cache[info->idx], info->file,
info->data.ptr, opts->metric->payload);
} else {
/* if we didn't initially know the size, we might have an odb_obj
* around from earlier, so convert that, otherwise load the blob now
*/
if (info->odb_obj != NULL)
error = git_object__from_odb_object(
(git_object **)&info->blob, info->repo,
info->odb_obj, GIT_OBJ_BLOB);
else
error = git_blob_lookup(&info->blob, info->repo, &file->oid);
git_buf_free(&path); if (error < 0) {
} else { /* compute hashsig from blob buffer */
git_blob *blob = NULL;
git_off_t blobsize;
/* TODO: add max size threshold a la diff? */
if (git_blob_lookup(&blob, diff->repo, &file->oid) < 0) {
/* if lookup fails, just skip this item in similarity calc */ /* if lookup fails, just skip this item in similarity calc */
giterr_clear(); giterr_clear();
return 0; } else {
} size_t sz;
blobsize = git_blob_rawsize(blob); /* index size may not be actual blob size if filtered */
if (!git__is_sizet(blobsize)) /* ? what to do ? */ if (file->size != git_blob_rawsize(info->blob))
blobsize = (size_t)-1; file->size = git_blob_rawsize(info->blob);
sz = (size_t)(git__is_sizet(file->size) ? file->size : -1);
error = opts->metric->buffer_signature( error = opts->metric->buffer_signature(
&cache[file_idx], file, git_blob_rawcontent(blob), &cache[info->idx], info->file,
(size_t)blobsize, opts->metric->payload); git_blob_rawcontent(info->blob), sz, opts->metric->payload);
}
git_blob_free(blob);
} }
return error; return error;
} }
static void similarity_unload(similarity_info *info)
{
if (info->odb_obj)
git_odb_object_free(info->odb_obj);
if (info->blob)
git_blob_free(info->blob);
else
git_buf_free(&info->data);
}
#define FLAG_SET(opts,flag_name) (((opts)->flags & flag_name) != 0) #define FLAG_SET(opts,flag_name) (((opts)->flags & flag_name) != 0)
/* - score < 0 means files cannot be compared /* - score < 0 means files cannot be compared
@ -476,6 +518,8 @@ static int similarity_measure(
git_diff_file *a_file = similarity_get_file(diff, a_idx); git_diff_file *a_file = similarity_get_file(diff, a_idx);
git_diff_file *b_file = similarity_get_file(diff, b_idx); git_diff_file *b_file = similarity_get_file(diff, b_idx);
bool exact_match = FLAG_SET(opts, GIT_DIFF_FIND_EXACT_MATCH_ONLY); bool exact_match = FLAG_SET(opts, GIT_DIFF_FIND_EXACT_MATCH_ONLY);
int error = 0;
similarity_info a_info, b_info;
*score = -1; *score = -1;
@ -483,7 +527,7 @@ static int similarity_measure(
if (GIT_MODE_TYPE(a_file->mode) != GIT_MODE_TYPE(b_file->mode)) if (GIT_MODE_TYPE(a_file->mode) != GIT_MODE_TYPE(b_file->mode))
return 0; return 0;
/* if exact match is requested, force calculation of missing OIDs */ /* if exact match is requested, force calculation of missing OIDs now */
if (exact_match) { if (exact_match) {
if (git_oid_iszero(&a_file->oid) && if (git_oid_iszero(&a_file->oid) &&
diff->old_src == GIT_ITERATOR_TYPE_WORKDIR && diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
@ -510,19 +554,44 @@ static int similarity_measure(
return 0; return 0;
} }
memset(&a_info, 0, sizeof(a_info));
memset(&b_info, 0, sizeof(b_info));
/* set up similarity data (will try to update missing file sizes) */
if (!cache[a_idx] && (error = similarity_init(&a_info, diff, a_idx)) < 0)
return error;
if (!cache[b_idx] && (error = similarity_init(&b_info, diff, b_idx)) < 0)
goto cleanup;
/* check if file sizes are nowhere near each other */
if (a_file->size > 127 &&
b_file->size > 127 &&
(a_file->size > (b_file->size << 3) ||
b_file->size > (a_file->size << 3)))
goto cleanup;
/* update signature cache if needed */ /* update signature cache if needed */
if (!cache[a_idx] && similarity_calc(diff, opts, a_idx, cache) < 0) if (!cache[a_idx]) {
return -1; if ((error = similarity_sig(&a_info, opts, cache)) < 0)
if (!cache[b_idx] && similarity_calc(diff, opts, b_idx, cache) < 0) goto cleanup;
return -1; }
if (!cache[b_idx]) {
if ((error = similarity_sig(&b_info, opts, cache)) < 0)
goto cleanup;
}
/* some metrics may not wish to process this file (too big / too small) */ /* calculate similarity provided that the metric choose to process
if (!cache[a_idx] || !cache[b_idx]) * both the a and b files (some may not if file is too big, etc).
return 0; */
if (cache[a_idx] && cache[b_idx])
/* compare signatures */ error = opts->metric->similarity(
return opts->metric->similarity(
score, cache[a_idx], cache[b_idx], opts->metric->payload); score, cache[a_idx], cache[b_idx], opts->metric->payload);
cleanup:
similarity_unload(&a_info);
similarity_unload(&b_info);
return error;
} }
static int calc_self_similarity( static int calc_self_similarity(
@ -590,11 +659,13 @@ static bool is_rename_target(
return false; return false;
case GIT_DELTA_UNTRACKED: case GIT_DELTA_UNTRACKED:
case GIT_DELTA_IGNORED:
if (!FLAG_SET(opts, GIT_DIFF_FIND_FOR_UNTRACKED)) if (!FLAG_SET(opts, GIT_DIFF_FIND_FOR_UNTRACKED))
return false; return false;
break; break;
case GIT_DELTA_IGNORED:
return false;
default: /* all other status values should be checked */ default: /* all other status values should be checked */
break; break;
} }
@ -673,6 +744,15 @@ GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta)
delta->status == GIT_DELTA_IGNORED); delta->status == GIT_DELTA_IGNORED);
} }
GIT_INLINE(void) delta_make_rename(
git_diff_delta *to, const git_diff_delta *from, uint32_t similarity)
{
to->status = GIT_DELTA_RENAMED;
to->similarity = similarity;
memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
}
typedef struct { typedef struct {
uint32_t idx; uint32_t idx;
uint32_t similarity; uint32_t similarity;
@ -682,85 +762,156 @@ int git_diff_find_similar(
git_diff_list *diff, git_diff_list *diff,
git_diff_find_options *given_opts) git_diff_find_options *given_opts)
{ {
size_t i, j, cache_size; size_t s, t;
int error = 0, similarity; int error = 0, similarity;
git_diff_delta *from, *to; git_diff_delta *src, *tgt;
git_diff_find_options opts; git_diff_find_options opts;
size_t num_rewrites = 0, num_updates = 0; size_t num_deltas, num_srcs = 0, num_tgts = 0;
void **cache; /* cache of similarity metric file signatures */ size_t tried_srcs = 0, tried_tgts = 0;
diff_find_match *match_sources, *match_targets; /* cache of best matches */ size_t num_rewrites = 0, num_updates = 0, num_bumped = 0;
void **sigcache; /* cache of similarity metric file signatures */
diff_find_match *tgt2src = NULL;
diff_find_match *src2tgt = NULL;
diff_find_match *tgt2src_copy = NULL;
diff_find_match *best_match;
git_diff_file swap;
if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0) if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0)
return error; return error;
num_deltas = diff->deltas.length;
/* TODO: maybe abort if deltas.length > rename_limit ??? */ /* TODO: maybe abort if deltas.length > rename_limit ??? */
if (!git__is_uint32(diff->deltas.length)) if (!git__is_uint32(num_deltas))
return 0; return 0;
cache_size = diff->deltas.length * 2; /* must store b/c length may change */ sigcache = git__calloc(num_deltas * 2, sizeof(void *));
cache = git__calloc(cache_size, sizeof(void *)); GITERR_CHECK_ALLOC(sigcache);
GITERR_CHECK_ALLOC(cache);
match_sources = git__calloc(diff->deltas.length, sizeof(diff_find_match)); /* Label rename sources and targets
match_targets = git__calloc(diff->deltas.length, sizeof(diff_find_match)); *
GITERR_CHECK_ALLOC(match_sources); * This will also set self-similarity scores for MODIFIED files and
GITERR_CHECK_ALLOC(match_targets); * mark them for splitting if break-rewrites is enabled
*/
git_vector_foreach(&diff->deltas, t, tgt) {
if (is_rename_source(diff, &opts, t, sigcache))
++num_srcs;
/* next find the most similar delta for each rename / copy candidate */ if (is_rename_target(diff, &opts, t, sigcache))
++num_tgts;
git_vector_foreach(&diff->deltas, i, to) { if ((tgt->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0)
size_t tried_sources = 0; num_rewrites++;
}
match_targets[i].idx = (uint32_t)i; /* if there are no candidate srcs or tgts, we're done */
match_targets[i].similarity = 0; if (!num_srcs || !num_tgts)
/* skip things that are not rename targets */
if (!is_rename_target(diff, &opts, i, cache))
continue;
git_vector_foreach(&diff->deltas, j, from) {
if (i == j)
continue;
/* skip things that are not rename sources */
if (!is_rename_source(diff, &opts, j, cache))
continue;
/* cap on maximum targets we'll examine (per "to" file) */
if (++tried_sources > opts.rename_limit)
break;
/* calculate similarity for this pair and find best match */
if ((error = similarity_measure(
&similarity, diff, &opts, cache, 2 * j, 2 * i + 1)) < 0)
goto cleanup; goto cleanup;
if (similarity < 0) { /* not actually comparable */ src2tgt = git__calloc(num_deltas, sizeof(diff_find_match));
--tried_sources; GITERR_CHECK_ALLOC(src2tgt);
tgt2src = git__calloc(num_deltas, sizeof(diff_find_match));
GITERR_CHECK_ALLOC(tgt2src);
if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) {
tgt2src_copy = git__calloc(num_deltas, sizeof(diff_find_match));
GITERR_CHECK_ALLOC(tgt2src_copy);
}
/*
* Find best-fit matches for rename / copy candidates
*/
find_best_matches:
tried_tgts = num_bumped = 0;
git_vector_foreach(&diff->deltas, t, tgt) {
/* skip things that are not rename targets */
if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0)
continue; continue;
tried_srcs = 0;
git_vector_foreach(&diff->deltas, s, src) {
/* skip things that are not rename sources */
if ((src->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) == 0)
continue;
/* calculate similarity for this pair and find best match */
if (s == t)
similarity = -1; /* don't measure self-similarity here */
else if ((error = similarity_measure(
&similarity, diff, &opts, sigcache, 2 * s, 2 * t + 1)) < 0)
goto cleanup;
if (similarity < 0)
continue;
/* is this a better rename? */
if (tgt2src[t].similarity < (uint32_t)similarity &&
src2tgt[s].similarity < (uint32_t)similarity)
{
/* eject old mapping */
if (src2tgt[s].similarity > 0) {
tgt2src[src2tgt[s].idx].similarity = 0;
num_bumped++;
}
if (tgt2src[t].similarity > 0) {
src2tgt[tgt2src[t].idx].similarity = 0;
num_bumped++;
} }
if (match_targets[i].similarity < (uint32_t)similarity && /* write new mapping */
match_sources[j].similarity < (uint32_t)similarity) { tgt2src[t].idx = (uint32_t)s;
match_targets[i].similarity = (uint32_t)similarity; tgt2src[t].similarity = (uint32_t)similarity;
match_sources[j].similarity = (uint32_t)similarity; src2tgt[s].idx = (uint32_t)t;
match_targets[i].idx = (uint32_t)j; src2tgt[s].similarity = (uint32_t)similarity;
match_sources[j].idx = (uint32_t)i;
}
}
} }
/* next rewrite the diffs with renames / copies */ /* keep best absolute match for copies */
if (tgt2src_copy != NULL &&
tgt2src_copy[t].similarity < (uint32_t)similarity)
{
tgt2src_copy[t].idx = (uint32_t)s;
tgt2src_copy[t].similarity = (uint32_t)similarity;
}
if (++tried_srcs >= num_srcs)
break;
/* cap on maximum targets we'll examine (per "tgt" file) */
if (tried_srcs > opts.rename_limit)
break;
}
if (++tried_tgts >= num_tgts)
break;
}
if (num_bumped > 0) /* try again if we bumped some items */
goto find_best_matches;
/*
* Rewrite the diffs with renames / copies
*/
tried_tgts = 0;
git_vector_foreach(&diff->deltas, t, tgt) {
/* skip things that are not rename targets */
if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0)
continue;
git_vector_foreach(&diff->deltas, i, to) {
/* check if this delta was the target of a similarity */ /* check if this delta was the target of a similarity */
if ((similarity = (int)match_targets[i].similarity) <= 0) if (tgt2src[t].similarity)
best_match = &tgt2src[t];
else if (tgt2src_copy && tgt2src_copy[t].similarity)
best_match = &tgt2src_copy[t];
else
continue; continue;
assert(to && (to->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) != 0); s = best_match->idx;
src = GIT_VECTOR_GET(&diff->deltas, s);
from = GIT_VECTOR_GET(&diff->deltas, match_targets[i].idx);
assert(from && (from->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) != 0);
/* possible scenarios: /* possible scenarios:
* 1. from DELETE to ADD/UNTRACK/IGNORE = RENAME * 1. from DELETE to ADD/UNTRACK/IGNORE = RENAME
@ -770,135 +921,137 @@ int git_diff_find_similar(
* 5. from OTHER to ADD/UNTRACK/IGNORE = OTHER + COPY * 5. from OTHER to ADD/UNTRACK/IGNORE = OTHER + COPY
*/ */
if (from->status == GIT_DELTA_DELETED) { if (src->status == GIT_DELTA_DELETED) {
if (delta_is_new_only(to)) { if (delta_is_new_only(tgt)) {
if (similarity < (int)opts.rename_threshold) if (best_match->similarity < opts.rename_threshold)
continue; continue;
from->status = GIT_DELTA_RENAMED; delta_make_rename(tgt, src, best_match->similarity);
from->similarity = (uint32_t)similarity;
memcpy(&from->new_file, &to->new_file, sizeof(from->new_file));
to->flags |= GIT_DIFF_FLAG__TO_DELETE;
src->flags |= GIT_DIFF_FLAG__TO_DELETE;
num_rewrites++; num_rewrites++;
} else { } else {
assert(delta_is_split(to)); assert(delta_is_split(tgt));
if (similarity < (int)opts.rename_from_rewrite_threshold) if (best_match->similarity < opts.rename_from_rewrite_threshold)
continue; continue;
from->status = GIT_DELTA_RENAMED; memcpy(&swap, &tgt->old_file, sizeof(swap));
from->similarity = (uint32_t)similarity;
memcpy(&from->new_file, &to->new_file, sizeof(from->new_file));
to->status = GIT_DELTA_DELETED; delta_make_rename(tgt, src, best_match->similarity);
memset(&to->new_file, 0, sizeof(to->new_file));
to->new_file.path = to->old_file.path;
to->new_file.flags |= GIT_DIFF_FLAG_VALID_OID;
if ((to->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) {
to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
num_rewrites--; num_rewrites--;
}
src->status = GIT_DELTA_DELETED;
memcpy(&src->old_file, &swap, sizeof(src->old_file));
memset(&src->new_file, 0, sizeof(src->new_file));
src->new_file.path = src->old_file.path;
src->new_file.flags |= GIT_DIFF_FLAG_VALID_OID;
num_updates++; num_updates++;
if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) {
/* what used to be at src t is now at src s */
tgt2src[src2tgt[t].idx].idx = (uint32_t)s;
}
} }
} }
else if (delta_is_split(from)) { else if (delta_is_split(src)) {
git_diff_file swap;
if (delta_is_new_only(to)) { if (delta_is_new_only(tgt)) {
if (similarity < (int)opts.rename_threshold) if (best_match->similarity < opts.rename_threshold)
continue; continue;
memcpy(&swap, &from->new_file, sizeof(swap)); delta_make_rename(tgt, src, best_match->similarity);
from->status = GIT_DELTA_RENAMED; src->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ?
from->similarity = (uint32_t)similarity;
memcpy(&from->new_file, &to->new_file, sizeof(from->new_file));
if ((from->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) {
from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
num_rewrites--;
}
to->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ?
GIT_DELTA_UNTRACKED : GIT_DELTA_ADDED; GIT_DELTA_UNTRACKED : GIT_DELTA_ADDED;
memcpy(&to->new_file, &swap, sizeof(to->new_file)); memset(&src->old_file, 0, sizeof(src->old_file));
to->old_file.path = to->new_file.path; src->old_file.path = src->new_file.path;
src->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;
src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
num_rewrites--;
num_updates++; num_updates++;
} else { } else {
assert(delta_is_split(from)); assert(delta_is_split(src));
if (similarity < (int)opts.rename_from_rewrite_threshold) if (best_match->similarity < opts.rename_from_rewrite_threshold)
continue; continue;
memcpy(&swap, &to->new_file, sizeof(swap)); memcpy(&swap, &tgt->old_file, sizeof(swap));
to->status = GIT_DELTA_RENAMED; delta_make_rename(tgt, src, best_match->similarity);
to->similarity = (uint32_t)similarity;
memcpy(&to->new_file, &from->new_file, sizeof(to->new_file));
if ((to->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) {
to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
num_rewrites--; num_rewrites--;
} num_updates++;
memcpy(&from->new_file, &swap, sizeof(from->new_file)); memcpy(&src->old_file, &swap, sizeof(src->old_file));
if ((from->flags & GIT_DIFF_FLAG__TO_SPLIT) == 0) {
from->flags |= GIT_DIFF_FLAG__TO_SPLIT;
num_rewrites++;
}
/* in the off chance that we've just swapped the new /* if we've just swapped the new element into the correct
* element into the correct place, clear the SPLIT flag * place, clear the SPLIT flag
*/ */
if (match_targets[match_targets[i].idx].idx == i && if (tgt2src[s].idx == t &&
match_targets[match_targets[i].idx].similarity > tgt2src[s].similarity >
opts.rename_from_rewrite_threshold) { opts.rename_from_rewrite_threshold) {
src->status = GIT_DELTA_RENAMED;
from->status = GIT_DELTA_RENAMED; src->similarity = tgt2src[s].similarity;
from->similarity = tgt2src[s].similarity = 0;
(uint32_t)match_targets[match_targets[i].idx].similarity; src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
match_targets[match_targets[i].idx].similarity = 0;
from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
num_rewrites--; num_rewrites--;
} }
/* otherwise, if we just overwrote a source, update mapping */
else if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) {
/* what used to be at src t is now at src s */
tgt2src[src2tgt[t].idx].idx = (uint32_t)s;
}
num_updates++; num_updates++;
} }
} }
else if (delta_is_new_only(to)) { else if (delta_is_new_only(tgt)) {
if (!FLAG_SET(&opts, GIT_DIFF_FIND_COPIES) || if (!FLAG_SET(&opts, GIT_DIFF_FIND_COPIES))
similarity < (int)opts.copy_threshold)
continue; continue;
to->status = GIT_DELTA_COPIED; if (tgt2src_copy[t].similarity < opts.copy_threshold)
to->similarity = (uint32_t)similarity; continue;
memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
/* always use best possible source for copy */
best_match = &tgt2src_copy[t];
src = GIT_VECTOR_GET(&diff->deltas, best_match->idx);
tgt->status = GIT_DELTA_COPIED;
tgt->similarity = best_match->similarity;
memcpy(&tgt->old_file, &src->old_file, sizeof(tgt->old_file));
num_updates++; num_updates++;
} }
} }
/*
* Actually split and delete entries as needed
*/
if (num_rewrites > 0 || num_updates > 0) if (num_rewrites > 0 || num_updates > 0)
error = apply_splits_and_deletes( error = apply_splits_and_deletes(
diff, diff->deltas.length - num_rewrites, diff, diff->deltas.length - num_rewrites,
FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES)); FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES) &&
!FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY));
cleanup: cleanup:
git__free(match_sources); git__free(tgt2src);
git__free(match_targets); git__free(src2tgt);
git__free(tgt2src_copy);
for (i = 0; i < cache_size; ++i) { for (t = 0; t < num_deltas * 2; ++t) {
if (cache[i] != NULL) if (sigcache[t] != NULL)
opts.metric->free_signature(cache[i], opts.metric->payload); opts.metric->free_signature(sigcache[t], opts.metric->payload);
} }
git__free(cache); git__free(sigcache);
if (!given_opts || !given_opts->metric) if (!given_opts || !given_opts->metric)
git__free(opts.metric); git__free(opts.metric);

View File

@ -53,7 +53,7 @@ static int lock_file(git_filebuf *file, int flags)
giterr_clear(); /* actual OS error code just confuses */ giterr_clear(); /* actual OS error code just confuses */
giterr_set(GITERR_OS, giterr_set(GITERR_OS,
"Failed to lock file '%s' for writing", file->path_lock); "Failed to lock file '%s' for writing", file->path_lock);
return -1; return GIT_ELOCKED;
} }
} }
@ -66,7 +66,7 @@ static int lock_file(git_filebuf *file, int flags)
} }
if (file->fd < 0) if (file->fd < 0)
return -1; return file->fd;
file->fd_is_open = true; file->fd_is_open = true;
@ -197,7 +197,7 @@ static int write_deflate(git_filebuf *file, void *source, size_t len)
int git_filebuf_open(git_filebuf *file, const char *path, int flags) int git_filebuf_open(git_filebuf *file, const char *path, int flags)
{ {
int compression; int compression, error = -1;
size_t path_len; size_t path_len;
/* opening an already open buffer is a programming error; /* opening an already open buffer is a programming error;
@ -282,7 +282,7 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags)
memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH); memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH);
/* open the file for locking */ /* open the file for locking */
if (lock_file(file, flags) < 0) if ((error = lock_file(file, flags)) < 0)
goto cleanup; goto cleanup;
} }
@ -290,7 +290,7 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags)
cleanup: cleanup:
git_filebuf_cleanup(file); git_filebuf_cleanup(file);
return -1; return error;
} }
int git_filebuf_hash(git_oid *oid, git_filebuf *file) int git_filebuf_hash(git_oid *oid, git_filebuf *file)

View File

@ -58,17 +58,19 @@ int git_futils_creat_locked(const char *path, const mode_t mode)
int fd; int fd;
#ifdef GIT_WIN32 #ifdef GIT_WIN32
wchar_t buf[GIT_WIN_PATH]; git_win32_path buf;
git__utf8_to_16(buf, GIT_WIN_PATH, path); git_win32_path_from_c(buf, path);
fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC |
O_EXCL | O_BINARY | O_CLOEXEC, mode);
#else #else
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); fd = open(path, O_WRONLY | O_CREAT | O_TRUNC |
O_EXCL | O_BINARY | O_CLOEXEC, mode);
#endif #endif
if (fd < 0) { if (fd < 0) {
giterr_set(GITERR_OS, "Failed to create locked file '%s'", path); giterr_set(GITERR_OS, "Failed to create locked file '%s'", path);
return -1; return errno == EEXIST ? GIT_ELOCKED : -1;
} }
return fd; return fd;
@ -108,7 +110,7 @@ git_off_t git_futils_filesize(git_file fd)
mode_t git_futils_canonical_mode(mode_t raw_mode) mode_t git_futils_canonical_mode(mode_t raw_mode)
{ {
if (S_ISREG(raw_mode)) if (S_ISREG(raw_mode))
return S_IFREG | GIT_CANONICAL_PERMS(raw_mode); return S_IFREG | GIT_PERMS_CANONICAL(raw_mode);
else if (S_ISLNK(raw_mode)) else if (S_ISLNK(raw_mode))
return S_IFLNK; return S_IFLNK;
else if (S_ISGITLINK(raw_mode)) else if (S_ISGITLINK(raw_mode))
@ -145,6 +147,7 @@ int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
int git_futils_readbuffer_updated( int git_futils_readbuffer_updated(
git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated) git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated)
{ {
int error = 0;
git_file fd; git_file fd;
struct stat st; struct stat st;
bool changed = false; bool changed = false;
@ -154,11 +157,15 @@ int git_futils_readbuffer_updated(
if (updated != NULL) if (updated != NULL)
*updated = 0; *updated = 0;
if ((fd = git_futils_open_ro(path)) < 0) if (p_stat(path, &st) < 0) {
return fd; error = errno;
giterr_set(GITERR_OS, "Failed to stat '%s'", path);
if (error == ENOENT || error == ENOTDIR)
return GIT_ENOTFOUND;
return -1;
}
if (p_fstat(fd, &st) < 0 || S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) { if (S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) {
p_close(fd);
giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path); giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path);
return -1; return -1;
} }
@ -175,7 +182,6 @@ int git_futils_readbuffer_updated(
changed = true; changed = true;
if (!changed) { if (!changed) {
p_close(fd);
return 0; return 0;
} }
@ -184,6 +190,9 @@ int git_futils_readbuffer_updated(
if (size != NULL) if (size != NULL)
*size = (size_t)st.st_size; *size = (size_t)st.st_size;
if ((fd = git_futils_open_ro(path)) < 0)
return fd;
if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) { if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) {
p_close(fd); p_close(fd);
return -1; return -1;
@ -220,6 +229,7 @@ int git_futils_writebuffer(
if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) { if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) {
giterr_set(GITERR_OS, "Could not write to '%s'", path); giterr_set(GITERR_OS, "Could not write to '%s'", path);
(void)p_close(fd); (void)p_close(fd);
return error;
} }
if ((error = p_close(fd)) < 0) if ((error = p_close(fd)) < 0)
@ -320,7 +330,7 @@ int git_futils_mkdir(
min_root_len = git_path_root(make_path.ptr); min_root_len = git_path_root(make_path.ptr);
if (root < min_root_len) if (root < min_root_len)
root = min_root_len; root = min_root_len;
while (make_path.ptr[root] == '/') while (root >= 0 && make_path.ptr[root] == '/')
++root; ++root;
/* clip root to make_path length */ /* clip root to make_path length */
@ -625,6 +635,18 @@ static git_futils_dirs_guess_cb git_futils__dir_guess[GIT_FUTILS_DIR__MAX] = {
git_futils_guess_xdg_dirs, git_futils_guess_xdg_dirs,
}; };
int git_futils_dirs_global_init(void)
{
git_futils_dir_t i;
const git_buf *path;
int error = 0;
for (i = 0; !error && i < GIT_FUTILS_DIR__MAX; i++)
error = git_futils_dirs_get(&path, i);
return error;
}
static int git_futils_check_selector(git_futils_dir_t which) static int git_futils_check_selector(git_futils_dir_t which)
{ {
if (which < GIT_FUTILS_DIR__MAX) if (which < GIT_FUTILS_DIR__MAX)
@ -847,6 +869,7 @@ typedef struct {
uint32_t flags; uint32_t flags;
uint32_t mkdir_flags; uint32_t mkdir_flags;
mode_t dirmode; mode_t dirmode;
int error;
} cp_r_info; } cp_r_info;
#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10) #define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10)
@ -885,20 +908,23 @@ static int _cp_r_callback(void *ref, git_buf *from)
return 0; return 0;
if (git_buf_joinpath( if (git_buf_joinpath(
&info->to, info->to_root, from->ptr + info->from_prefix) < 0) &info->to, info->to_root, from->ptr + info->from_prefix) < 0) {
return -1; error = -1;
goto exit;
}
if (p_lstat(info->to.ptr, &to_st) < 0) { if (p_lstat(info->to.ptr, &to_st) < 0) {
if (errno != ENOENT && errno != ENOTDIR) { if (errno != ENOENT && errno != ENOTDIR) {
giterr_set(GITERR_OS, giterr_set(GITERR_OS,
"Could not access %s while copying files", info->to.ptr); "Could not access %s while copying files", info->to.ptr);
return -1; error = -1;
goto exit;
} }
} else } else
exists = true; exists = true;
if ((error = git_path_lstat(from->ptr, &from_st)) < 0) if ((error = git_path_lstat(from->ptr, &from_st)) < 0)
return error; goto exit;
if (S_ISDIR(from_st.st_mode)) { if (S_ISDIR(from_st.st_mode)) {
mode_t oldmode = info->dirmode; mode_t oldmode = info->dirmode;
@ -912,13 +938,14 @@ static int _cp_r_callback(void *ref, git_buf *from)
error = _cp_r_mkdir(info, from); error = _cp_r_mkdir(info, from);
/* recurse onto target directory */ /* recurse onto target directory */
if (!error && (!exists || S_ISDIR(to_st.st_mode))) if (!error && (!exists || S_ISDIR(to_st.st_mode)) &&
error = git_path_direach(from, _cp_r_callback, info); ((error = git_path_direach(from, _cp_r_callback, info)) == GIT_EUSER))
error = info->error;
if (oldmode != 0) if (oldmode != 0)
info->dirmode = oldmode; info->dirmode = oldmode;
return error; goto exit;
} }
if (exists) { if (exists) {
@ -928,7 +955,8 @@ static int _cp_r_callback(void *ref, git_buf *from)
if (p_unlink(info->to.ptr) < 0) { if (p_unlink(info->to.ptr) < 0) {
giterr_set(GITERR_OS, "Cannot overwrite existing file '%s'", giterr_set(GITERR_OS, "Cannot overwrite existing file '%s'",
info->to.ptr); info->to.ptr);
return -1; error = -1;
goto exit;
} }
} }
@ -941,7 +969,7 @@ static int _cp_r_callback(void *ref, git_buf *from)
/* Make container directory on demand if needed */ /* Make container directory on demand if needed */
if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 && if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 &&
(error = _cp_r_mkdir(info, from)) < 0) (error = _cp_r_mkdir(info, from)) < 0)
return error; goto exit;
/* make symlink or regular file */ /* make symlink or regular file */
if (S_ISLNK(from_st.st_mode)) if (S_ISLNK(from_st.st_mode))
@ -950,11 +978,13 @@ static int _cp_r_callback(void *ref, git_buf *from)
mode_t usemode = from_st.st_mode; mode_t usemode = from_st.st_mode;
if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0) if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
usemode = (usemode & 0111) ? 0777 : 0666; usemode = GIT_PERMS_FOR_WRITE(usemode);
error = git_futils_cp(from->ptr, info->to.ptr, usemode); error = git_futils_cp(from->ptr, info->to.ptr, usemode);
} }
exit:
info->error = error;
return error; return error;
} }
@ -975,6 +1005,7 @@ int git_futils_cp_r(
info.flags = flags; info.flags = flags;
info.dirmode = dirmode; info.dirmode = dirmode;
info.from_prefix = path.size; info.from_prefix = path.size;
info.error = 0;
git_buf_init(&info.to, 0); git_buf_init(&info.to, 0);
/* precalculate mkdir flags */ /* precalculate mkdir flags */
@ -996,6 +1027,9 @@ int git_futils_cp_r(
git_buf_free(&path); git_buf_free(&path);
git_buf_free(&info.to); git_buf_free(&info.to);
if (error == GIT_EUSER)
error = info.error;
return error; return error;
} }

View File

@ -223,9 +223,13 @@ extern int git_futils_open_ro(const char *path);
*/ */
extern git_off_t git_futils_filesize(git_file fd); extern git_off_t git_futils_filesize(git_file fd);
#define GIT_PERMS_IS_EXEC(MODE) (((MODE) & 0111) != 0)
#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0755 : 0644)
#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0777 : 0666)
#define GIT_MODE_PERMS_MASK 0777 #define GIT_MODE_PERMS_MASK 0777
#define GIT_CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644) #define GIT_MODE_TYPE_MASK 0170000
#define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK) #define GIT_MODE_TYPE(MODE) ((MODE) & GIT_MODE_TYPE_MASK)
#define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB)) #define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB))
/** /**
@ -244,7 +248,7 @@ extern mode_t git_futils_canonical_mode(mode_t raw_mode);
* @param out buffer to populate with the mapping information. * @param out buffer to populate with the mapping information.
* @param fd open descriptor to configure the mapping from. * @param fd open descriptor to configure the mapping from.
* @param begin first byte to map, this should be page aligned. * @param begin first byte to map, this should be page aligned.
* @param end number of bytes to map. * @param len number of bytes to map.
* @return * @return
* - 0 on success; * - 0 on success;
* - -1 on error. * - -1 on error.
@ -278,7 +282,7 @@ extern void git_futils_mmap_free(git_map *map);
/** /**
* Find a "global" file (i.e. one in a user's home directory). * Find a "global" file (i.e. one in a user's home directory).
* *
* @param pathbuf buffer to write the full path into * @param path buffer to write the full path into
* @param filename name of file to find in the home directory * @param filename name of file to find in the home directory
* @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
*/ */
@ -287,7 +291,7 @@ extern int git_futils_find_global_file(git_buf *path, const char *filename);
/** /**
* Find an "XDG" file (i.e. one in user's XDG config path). * Find an "XDG" file (i.e. one in user's XDG config path).
* *
* @param pathbuf buffer to write the full path into * @param path buffer to write the full path into
* @param filename name of file to find in the home directory * @param filename name of file to find in the home directory
* @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
*/ */
@ -296,7 +300,7 @@ extern int git_futils_find_xdg_file(git_buf *path, const char *filename);
/** /**
* Find a "system" file (i.e. one shared for all users of the system). * Find a "system" file (i.e. one shared for all users of the system).
* *
* @param pathbuf buffer to write the full path into * @param path buffer to write the full path into
* @param filename name of file to find in the home directory * @param filename name of file to find in the home directory
* @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
*/ */
@ -309,6 +313,13 @@ typedef enum {
GIT_FUTILS_DIR__MAX = 3, GIT_FUTILS_DIR__MAX = 3,
} git_futils_dir_t; } git_futils_dir_t;
/**
* Configures global data for configuration file search paths.
*
* @return 0 on success, <0 on failure
*/
extern int git_futils_dirs_global_init(void);
/** /**
* Get the search path for global/system/xdg files * Get the search path for global/system/xdg files
* *

View File

@ -65,26 +65,28 @@ int git_threads_init(void)
return -1; return -1;
/* Initialize any other subsystems that have global state */ /* Initialize any other subsystems that have global state */
if ((error = git_hash_global_init()) >= 0) if ((error = git_hash_global_init()) >= 0 &&
_tls_init = 1; (error = git_futils_dirs_global_init()) >= 0)
if (error == 0)
_tls_init = 1; _tls_init = 1;
GIT_MEMORY_BARRIER; GIT_MEMORY_BARRIER;
win32_pthread_initialize();
return error; return error;
} }
void git_threads_shutdown(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();
TlsFree(_tls_index); TlsFree(_tls_index);
_tls_init = 0; _tls_init = 0;
git_mutex_free(&git__mwindow_mutex);
/* Shut down any subsystems that have global state */ git_mutex_free(&git__mwindow_mutex);
git_hash_global_shutdown();
git_futils_dirs_free();
} }
git_global_st *git__global_state(void) git_global_st *git__global_state(void)
@ -127,7 +129,8 @@ int git_threads_init(void)
pthread_key_create(&_tls_key, &cb__free_status); pthread_key_create(&_tls_key, &cb__free_status);
/* Initialize any other subsystems that have global state */ /* Initialize any other subsystems that have global state */
if ((error = git_hash_global_init()) >= 0) if ((error = git_hash_global_init()) >= 0 &&
(error = git_futils_dirs_global_init()) >= 0)
_tls_init = 1; _tls_init = 1;
GIT_MEMORY_BARRIER; GIT_MEMORY_BARRIER;

View File

@ -20,33 +20,16 @@ static struct git_hash_prov hash_prov = {0};
/* Initialize CNG, if available */ /* Initialize CNG, if available */
GIT_INLINE(int) hash_cng_prov_init(void) GIT_INLINE(int) hash_cng_prov_init(void)
{ {
OSVERSIONINFOEX version_test = {0};
DWORD version_test_mask;
DWORDLONG version_condition_mask = 0;
char dll_path[MAX_PATH]; char dll_path[MAX_PATH];
DWORD dll_path_len, size_len; DWORD dll_path_len, size_len;
return -1;
/* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */ /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */
version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if (!git_has_win32_version(6, 0, 1))
version_test.dwMajorVersion = 6;
version_test.dwMinorVersion = 0;
version_test.wServicePackMajor = 1;
version_test.wServicePackMinor = 0;
version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR);
VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL);
VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);
VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL);
if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask))
return -1; return -1;
/* Load bcrypt.dll explicitly from the system directory */ /* Load bcrypt.dll explicitly from the system directory */
if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || dll_path_len > MAX_PATH || if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 ||
dll_path_len > MAX_PATH ||
StringCchCat(dll_path, MAX_PATH, "\\") < 0 || StringCchCat(dll_path, MAX_PATH, "\\") < 0 ||
StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 || StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 ||
(hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL) (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL)

View File

@ -13,12 +13,15 @@ typedef uint64_t hashsig_state;
#define HASHSIG_SCALE 100 #define HASHSIG_SCALE 100
#define HASHSIG_HASH_WINDOW 32 #define HASHSIG_MAX_RUN 80
#define HASHSIG_HASH_START 0 #define HASHSIG_HASH_START 0x012345678ABCDEF0LL
#define HASHSIG_HASH_SHIFT 5 #define HASHSIG_HASH_SHIFT 5
#define HASHSIG_HASH_MASK 0x7FFFFFFF
#define HASHSIG_HASH_MIX(S,CH) \
(S) = ((S) << HASHSIG_HASH_SHIFT) - (S) + (hashsig_state)(CH)
#define HASHSIG_HEAP_SIZE ((1 << 7) - 1) #define HASHSIG_HEAP_SIZE ((1 << 7) - 1)
#define HASHSIG_HEAP_MIN_SIZE 4
typedef int (*hashsig_cmp)(const void *a, const void *b, void *); typedef int (*hashsig_cmp)(const void *a, const void *b, void *);
@ -28,14 +31,6 @@ typedef struct {
hashsig_t values[HASHSIG_HEAP_SIZE]; hashsig_t values[HASHSIG_HEAP_SIZE];
} hashsig_heap; } hashsig_heap;
typedef struct {
hashsig_state state, shift_n;
char window[HASHSIG_HASH_WINDOW];
int win_len, win_pos, saw_lf;
} hashsig_in_progress;
#define HASHSIG_IN_PROGRESS_INIT { HASHSIG_HASH_START, 1, {0}, 0, 0, 1 }
struct git_hashsig { struct git_hashsig {
hashsig_heap mins; hashsig_heap mins;
hashsig_heap maxs; hashsig_heap maxs;
@ -43,8 +38,8 @@ struct git_hashsig {
int considered; int considered;
}; };
#define HEAP_LCHILD_OF(I) (((I)*2)+1) #define HEAP_LCHILD_OF(I) (((I)<<1)+1)
#define HEAP_RCHILD_OF(I) (((I)*2)+2) #define HEAP_RCHILD_OF(I) (((I)<<1)+2)
#define HEAP_PARENT_OF(I) (((I)-1)>>1) #define HEAP_PARENT_OF(I) (((I)-1)>>1)
static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp) static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp)
@ -115,134 +110,109 @@ static void hashsig_heap_sort(hashsig_heap *h)
static void hashsig_heap_insert(hashsig_heap *h, hashsig_t val) static void hashsig_heap_insert(hashsig_heap *h, hashsig_t val)
{ {
/* if heap is full, pop top if new element should replace it */
if (h->size == h->asize && h->cmp(&val, &h->values[0], NULL) > 0) {
h->size--;
h->values[0] = h->values[h->size];
hashsig_heap_down(h, 0);
}
/* if heap is not full, insert new element */ /* if heap is not full, insert new element */
if (h->size < h->asize) { if (h->size < h->asize) {
h->values[h->size++] = val; h->values[h->size++] = val;
hashsig_heap_up(h, h->size - 1); hashsig_heap_up(h, h->size - 1);
} }
/* if heap is full, pop top if new element should replace it */
else if (h->cmp(&val, &h->values[0], NULL) > 0) {
h->size--;
h->values[0] = h->values[h->size];
hashsig_heap_down(h, 0);
} }
GIT_INLINE(bool) hashsig_include_char( }
char ch, git_hashsig_option_t opt, int *saw_lf)
typedef struct {
int use_ignores;
uint8_t ignore_ch[256];
} hashsig_in_progress;
static void hashsig_in_progress_init(
hashsig_in_progress *prog, git_hashsig *sig)
{ {
if ((opt & GIT_HASHSIG_IGNORE_WHITESPACE) && git__isspace(ch)) int i;
return false;
if (opt & GIT_HASHSIG_SMART_WHITESPACE) { switch (sig->opt) {
if (ch == '\r' || (*saw_lf && git__isspace(ch))) case GIT_HASHSIG_IGNORE_WHITESPACE:
return false; for (i = 0; i < 256; ++i)
prog->ignore_ch[i] = git__isspace_nonlf(i);
*saw_lf = (ch == '\n'); prog->use_ignores = 1;
break;
case GIT_HASHSIG_SMART_WHITESPACE:
for (i = 0; i < 256; ++i)
prog->ignore_ch[i] = git__isspace(i);
prog->use_ignores = 1;
break;
default:
memset(prog, 0, sizeof(*prog));
break;
}
} }
return true; #define HASHSIG_IN_PROGRESS_INIT { 1 }
}
static void hashsig_initial_window(
git_hashsig *sig,
const char **data,
size_t size,
hashsig_in_progress *prog)
{
hashsig_state state, shift_n;
int win_len;
const char *scan, *end;
/* init until we have processed at least HASHSIG_HASH_WINDOW data */
if (prog->win_len >= HASHSIG_HASH_WINDOW)
return;
state = prog->state;
win_len = prog->win_len;
shift_n = prog->shift_n;
scan = *data;
end = scan + size;
while (scan < end && win_len < HASHSIG_HASH_WINDOW) {
char ch = *scan++;
if (!hashsig_include_char(ch, sig->opt, &prog->saw_lf))
continue;
state = (state * HASHSIG_HASH_SHIFT + ch) & HASHSIG_HASH_MASK;
if (!win_len)
shift_n = 1;
else
shift_n = (shift_n * HASHSIG_HASH_SHIFT) & HASHSIG_HASH_MASK;
prog->window[win_len++] = ch;
}
/* insert initial hash if we just finished */
if (win_len == HASHSIG_HASH_WINDOW) {
hashsig_heap_insert(&sig->mins, (hashsig_t)state);
hashsig_heap_insert(&sig->maxs, (hashsig_t)state);
sig->considered = 1;
}
prog->state = state;
prog->win_len = win_len;
prog->shift_n = shift_n;
*data = scan;
}
static int hashsig_add_hashes( static int hashsig_add_hashes(
git_hashsig *sig, git_hashsig *sig,
const char *data, const uint8_t *data,
size_t size, size_t size,
hashsig_in_progress *prog) hashsig_in_progress *prog)
{ {
const char *scan = data, *end = data + size; const uint8_t *scan = data, *end = data + size;
hashsig_state state, shift_n, rmv; hashsig_state state = HASHSIG_HASH_START;
int use_ignores = prog->use_ignores, len;
uint8_t ch;
if (prog->win_len < HASHSIG_HASH_WINDOW) while (scan < end) {
hashsig_initial_window(sig, &scan, size, prog); state = HASHSIG_HASH_START;
state = prog->state; for (len = 0; scan < end && len < HASHSIG_MAX_RUN; ) {
shift_n = prog->shift_n; ch = *scan;
/* advance window, adding new chars and removing old */ if (use_ignores)
for (; scan < end && git__isspace_nonlf(ch); ch = *scan)
++scan;
else if (sig->opt != GIT_HASHSIG_NORMAL)
for (; scan < end && ch == '\r'; ch = *scan)
++scan;
for (; scan < end; ++scan) { /* peek at next character to decide what to do next */
char ch = *scan; if (sig->opt == GIT_HASHSIG_SMART_WHITESPACE)
use_ignores = (ch == '\n');
if (!hashsig_include_char(ch, sig->opt, &prog->saw_lf)) if (scan >= end)
continue; break;
++scan;
rmv = shift_n * prog->window[prog->win_pos]; /* check run terminator */
if (ch == '\n' || ch == '\0')
break;
state = (state - rmv) & HASHSIG_HASH_MASK; ++len;
state = (state * HASHSIG_HASH_SHIFT) & HASHSIG_HASH_MASK; HASHSIG_HASH_MIX(state, ch);
state = (state + ch) & HASHSIG_HASH_MASK;
hashsig_heap_insert(&sig->mins, (hashsig_t)state);
hashsig_heap_insert(&sig->maxs, (hashsig_t)state);
sig->considered++;
prog->window[prog->win_pos] = ch;
prog->win_pos = (prog->win_pos + 1) % HASHSIG_HASH_WINDOW;
} }
prog->state = state; if (len > 0) {
hashsig_heap_insert(&sig->mins, (hashsig_t)state);
hashsig_heap_insert(&sig->maxs, (hashsig_t)state);
sig->considered++;
while (scan < end && (*scan == '\n' || !*scan))
++scan;
}
}
prog->use_ignores = use_ignores;
return 0; return 0;
} }
static int hashsig_finalize_hashes(git_hashsig *sig) static int hashsig_finalize_hashes(git_hashsig *sig)
{ {
if (sig->mins.size < HASHSIG_HEAP_SIZE) { if (sig->mins.size < HASHSIG_HEAP_MIN_SIZE) {
giterr_set(GITERR_INVALID, giterr_set(GITERR_INVALID,
"File too small for similarity signature calculation"); "File too small for similarity signature calculation");
return GIT_EBUFS; return GIT_EBUFS;
@ -274,11 +244,13 @@ int git_hashsig_create(
git_hashsig_option_t opts) git_hashsig_option_t opts)
{ {
int error; int error;
hashsig_in_progress prog = HASHSIG_IN_PROGRESS_INIT; hashsig_in_progress prog;
git_hashsig *sig = hashsig_alloc(opts); git_hashsig *sig = hashsig_alloc(opts);
GITERR_CHECK_ALLOC(sig); GITERR_CHECK_ALLOC(sig);
error = hashsig_add_hashes(sig, buf, buflen, &prog); hashsig_in_progress_init(&prog, sig);
error = hashsig_add_hashes(sig, (const uint8_t *)buf, buflen, &prog);
if (!error) if (!error)
error = hashsig_finalize_hashes(sig); error = hashsig_finalize_hashes(sig);
@ -296,10 +268,10 @@ int git_hashsig_create_fromfile(
const char *path, const char *path,
git_hashsig_option_t opts) git_hashsig_option_t opts)
{ {
char buf[4096]; uint8_t buf[0x1000];
ssize_t buflen = 0; ssize_t buflen = 0;
int error = 0, fd; int error = 0, fd;
hashsig_in_progress prog = HASHSIG_IN_PROGRESS_INIT; hashsig_in_progress prog;
git_hashsig *sig = hashsig_alloc(opts); git_hashsig *sig = hashsig_alloc(opts);
GITERR_CHECK_ALLOC(sig); GITERR_CHECK_ALLOC(sig);
@ -308,6 +280,8 @@ int git_hashsig_create_fromfile(
return fd; return fd;
} }
hashsig_in_progress_init(&prog, sig);
while (!error) { while (!error) {
if ((buflen = p_read(fd, buf, sizeof(buf))) <= 0) { if ((buflen = p_read(fd, buf, sizeof(buf))) <= 0) {
if ((error = (int)buflen) < 0) if ((error = (int)buflen) < 0)
@ -362,6 +336,12 @@ static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b)
int git_hashsig_compare(const git_hashsig *a, const git_hashsig *b) int git_hashsig_compare(const git_hashsig *a, const git_hashsig *b)
{ {
/* if we have fewer than the maximum number of elements, then just use
* one array since the two arrays will be the same
*/
if (a->mins.size < HASHSIG_HEAP_SIZE)
return hashsig_heap_compare(&a->mins, &b->mins);
else
return (hashsig_heap_compare(&a->mins, &b->mins) + return (hashsig_heap_compare(&a->mins, &b->mins) +
hashsig_heap_compare(&a->maxs, &b->maxs)) / 2; hashsig_heap_compare(&a->maxs, &b->maxs)) / 2;
} }

View File

@ -37,7 +37,7 @@ static int parse_ignore_file(
GITERR_CHECK_ALLOC(match); GITERR_CHECK_ALLOC(match);
} }
match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE; match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
if (!(error = git_attr_fnmatch__parse( if (!(error = git_attr_fnmatch__parse(
match, ignores->pool, context, &scan))) match, ignores->pool, context, &scan)))
@ -159,7 +159,7 @@ int git_ignore__push_dir(git_ignores *ign, const char *dir)
{ {
if (git_buf_joinpath(&ign->dir, ign->dir.ptr, dir) < 0) if (git_buf_joinpath(&ign->dir, ign->dir.ptr, dir) < 0)
return -1; return -1;
else
return push_ignore_file( return push_ignore_file(
ign->repo, ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE); ign->repo, ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
} }
@ -168,8 +168,27 @@ int git_ignore__pop_dir(git_ignores *ign)
{ {
if (ign->ign_path.length > 0) { if (ign->ign_path.length > 0) {
git_attr_file *file = git_vector_last(&ign->ign_path); git_attr_file *file = git_vector_last(&ign->ign_path);
if (git__suffixcmp(ign->dir.ptr, file->key + 2) == 0) const char *start, *end, *scan;
size_t keylen;
/* - ign->dir looks something like "a/b" (or "a/b/c/d")
* - file->key looks something like "0#a/b/.gitignore
*
* We are popping the last directory off ign->dir. We also want to
* remove the file from the vector if the directory part of the key
* matches the ign->dir path. We need to test if the "a/b" part of
* the file key matches the path we are about to pop.
*/
for (start = end = scan = &file->key[2]; *scan; ++scan)
if (*scan == '/')
end = scan; /* point 'end' to last '/' in key */
keylen = (end - start) + 1;
if (ign->dir.size >= keylen &&
!memcmp(ign->dir.ptr + ign->dir.size - keylen, start, keylen))
git_vector_pop(&ign->ign_path); git_vector_pop(&ign->ign_path);
git_buf_rtruncate_at_char(&ign->dir, '/'); git_buf_rtruncate_at_char(&ign->dir, '/');
} }
return 0; return 0;
@ -298,12 +317,9 @@ int git_ignore_path_is_ignored(
path.full.size = (tail - path.full.ptr); path.full.size = (tail - path.full.ptr);
path.is_dir = (tail == end) ? full_is_dir : true; path.is_dir = (tail == end) ? full_is_dir : true;
/* update ignores for new path fragment */ /* initialize ignores the first time through */
if (path.basename == path.path) if (path.basename == path.path &&
error = git_ignore__for_path(repo, path.path, &ignores); (error = git_ignore__for_path(repo, path.path, &ignores)) < 0)
else
error = git_ignore__push_dir(&ignores, path.basename);
if (error < 0)
break; break;
/* first process builtins - success means path was found */ /* first process builtins - success means path was found */
@ -327,6 +343,10 @@ int git_ignore_path_is_ignored(
if (tail == end) if (tail == end)
break; break;
/* now add this directory to list of ignores */
if ((error = git_ignore__push_dir(&ignores, path.path)) < 0)
break;
/* reinstate divider in path */ /* reinstate divider in path */
*tail = '/'; *tail = '/';
while (*tail == '/') tail++; while (*tail == '/') tail++;
@ -340,3 +360,61 @@ cleanup:
return error; return error;
} }
int git_ignore__check_pathspec_for_exact_ignores(
git_repository *repo,
git_vector *vspec,
bool no_fnmatch)
{
int error = 0;
size_t i;
git_attr_fnmatch *match;
int ignored;
git_buf path = GIT_BUF_INIT;
const char *wd, *filename;
git_index *idx;
if ((error = git_repository__ensure_not_bare(
repo, "validate pathspec")) < 0 ||
(error = git_repository_index(&idx, repo)) < 0)
return error;
wd = git_repository_workdir(repo);
git_vector_foreach(vspec, i, match) {
/* skip wildcard matches (if they are being used) */
if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 &&
!no_fnmatch)
continue;
filename = match->pattern;
/* if file is already in the index, it's fine */
if (git_index_get_bypath(idx, filename, 0) != NULL)
continue;
if ((error = git_buf_joinpath(&path, wd, filename)) < 0)
break;
/* is there a file on disk that matches this exactly? */
if (!git_path_isfile(path.ptr))
continue;
/* is that file ignored? */
if ((error = git_ignore_path_is_ignored(&ignored, repo, filename)) < 0)
break;
if (ignored) {
giterr_set(GITERR_INVALID, "pathspec contains ignored file '%s'",
filename);
error = GIT_EINVALIDSPEC;
break;
}
}
git_index_free(idx);
git_buf_free(&path);
return error;
}

View File

@ -24,14 +24,15 @@
*/ */
typedef struct { typedef struct {
git_repository *repo; git_repository *repo;
git_buf dir; git_buf dir; /* current directory reflected in ign_path */
git_attr_file *ign_internal; git_attr_file *ign_internal;
git_vector ign_path; git_vector ign_path;
git_vector ign_global; git_vector ign_global;
int ignore_case; int ignore_case;
} git_ignores; } git_ignores;
extern int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ign); extern int git_ignore__for_path(
git_repository *repo, const char *path, git_ignores *ign);
extern int git_ignore__push_dir(git_ignores *ign, const char *dir); extern int git_ignore__push_dir(git_ignores *ign, const char *dir);
@ -41,4 +42,13 @@ extern void git_ignore__free(git_ignores *ign);
extern int git_ignore__lookup(git_ignores *ign, const char *path, int *ignored); extern int git_ignore__lookup(git_ignores *ign, const char *path, int *ignored);
/* command line Git sometimes generates an error message if given a
* pathspec that contains an exact match to an ignored file (provided
* --force isn't also given). This makes it easy to check it that has
* happened. Returns GIT_EINVALIDSPEC if the pathspec contains ignored
* exact matches (that are not already present in the index).
*/
extern int git_ignore__check_pathspec_for_exact_ignores(
git_repository *repo, git_vector *pathspec, bool no_fnmatch);
#endif #endif

View File

@ -15,6 +15,9 @@
#include "hash.h" #include "hash.h"
#include "iterator.h" #include "iterator.h"
#include "pathspec.h" #include "pathspec.h"
#include "ignore.h"
#include "blob.h"
#include "git2/odb.h" #include "git2/odb.h"
#include "git2/oid.h" #include "git2/oid.h"
#include "git2/blob.h" #include "git2/blob.h"
@ -99,8 +102,6 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
static bool is_index_extended(git_index *index); static bool is_index_extended(git_index *index);
static int write_index(git_index *index, git_filebuf *file); static int write_index(git_index *index, git_filebuf *file);
static int index_find(size_t *at_pos, git_index *index, const char *path, int stage);
static void index_entry_free(git_index_entry *entry); static void index_entry_free(git_index_entry *entry);
static void index_entry_reuc_free(git_index_reuc_entry *reuc); static void index_entry_reuc_free(git_index_reuc_entry *reuc);
@ -112,7 +113,7 @@ static int index_srch(const void *key, const void *array_member)
ret = strcmp(srch_key->path, entry->path); ret = strcmp(srch_key->path, entry->path);
if (ret == 0) if (ret == 0 && srch_key->stage != GIT_INDEX_STAGE_ANY)
ret = srch_key->stage - GIT_IDXENTRY_STAGE(entry); ret = srch_key->stage - GIT_IDXENTRY_STAGE(entry);
return ret; return ret;
@ -126,7 +127,7 @@ static int index_isrch(const void *key, const void *array_member)
ret = strcasecmp(srch_key->path, entry->path); ret = strcasecmp(srch_key->path, entry->path);
if (ret == 0) if (ret == 0 && srch_key->stage != GIT_INDEX_STAGE_ANY)
ret = srch_key->stage - GIT_IDXENTRY_STAGE(entry); ret = srch_key->stage - GIT_IDXENTRY_STAGE(entry);
return ret; return ret;
@ -259,6 +260,22 @@ static int reuc_icmp(const void *a, const void *b)
return strcasecmp(info_a->path, info_b->path); return strcasecmp(info_a->path, info_b->path);
} }
static void index_entry_reuc_free(git_index_reuc_entry *reuc)
{
if (!reuc)
return;
git__free(reuc->path);
git__free(reuc);
}
static void index_entry_free(git_index_entry *entry)
{
if (!entry)
return;
git__free(entry->path);
git__free(entry);
}
static unsigned int index_create_mode(unsigned int mode) static unsigned int index_create_mode(unsigned int mode)
{ {
if (S_ISLNK(mode)) if (S_ISLNK(mode))
@ -267,7 +284,7 @@ static unsigned int index_create_mode(unsigned int mode)
if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR)) if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR))
return (S_IFLNK | S_IFDIR); return (S_IFLNK | S_IFDIR);
return S_IFREG | ((mode & 0100) ? 0755 : 0644); return S_IFREG | GIT_PERMS_CANONICAL(mode);
} }
static unsigned int index_merge_mode( static unsigned int index_merge_mode(
@ -288,16 +305,16 @@ void git_index__set_ignore_case(git_index *index, bool ignore_case)
{ {
index->ignore_case = ignore_case; index->ignore_case = ignore_case;
index->entries._cmp = ignore_case ? index_icmp : index_cmp;
index->entries_cmp_path = ignore_case ? index_icmp_path : index_cmp_path; index->entries_cmp_path = ignore_case ? index_icmp_path : index_cmp_path;
index->entries_search = ignore_case ? index_isrch : index_srch; index->entries_search = ignore_case ? index_isrch : index_srch;
index->entries_search_path = ignore_case ? index_isrch_path : index_srch_path; index->entries_search_path = ignore_case ? index_isrch_path : index_srch_path;
index->entries.sorted = 0;
git_vector_set_cmp(&index->entries, ignore_case ? index_icmp : index_cmp);
git_vector_sort(&index->entries); git_vector_sort(&index->entries);
index->reuc._cmp = ignore_case ? reuc_icmp : reuc_cmp;
index->reuc_search = ignore_case ? reuc_isrch : reuc_srch; index->reuc_search = ignore_case ? reuc_isrch : reuc_srch;
index->reuc.sorted = 0;
git_vector_set_cmp(&index->reuc, ignore_case ? reuc_icmp : reuc_cmp);
git_vector_sort(&index->reuc); git_vector_sort(&index->reuc);
} }
@ -365,11 +382,8 @@ static void index_entries_free(git_vector *entries)
{ {
size_t i; size_t i;
for (i = 0; i < entries->length; ++i) { for (i = 0; i < entries->length; ++i)
git_index_entry *e = git_vector_get(entries, i); index_entry_free(git__swap(entries->contents[i], NULL));
git__free(e->path);
git__free(e);
}
git_vector_clear(entries); git_vector_clear(entries);
} }
@ -484,8 +498,12 @@ int git_index_write(git_index *index)
git_vector_sort(&index->reuc); git_vector_sort(&index->reuc);
if ((error = git_filebuf_open( if ((error = git_filebuf_open(
&file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < 0) &file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < 0) {
if (error == GIT_ELOCKED)
giterr_set(GITERR_INDEX, "The index is locked. This might be due to a concurrrent or crashed process");
return error; return error;
}
if ((error = write_index(index, &file)) < 0) { if ((error = write_index(index, &file)) < 0) {
git_filebuf_cleanup(&file); git_filebuf_cleanup(&file);
@ -503,6 +521,12 @@ int git_index_write(git_index *index)
return 0; return 0;
} }
const char * git_index_path(git_index *index)
{
assert(index);
return index->index_file_path;
}
int git_index_write_tree(git_oid *oid, git_index *index) int git_index_write_tree(git_oid *oid, git_index *index)
{ {
git_repository *repo; git_repository *repo;
@ -547,7 +571,7 @@ const git_index_entry *git_index_get_bypath(
git_vector_sort(&index->entries); git_vector_sort(&index->entries);
if (index_find(&pos, index, path, stage) < 0) { if (git_index__find(&pos, index, path, stage) < 0) {
giterr_set(GITERR_INDEX, "Index does not contain %s", path); giterr_set(GITERR_INDEX, "Index does not contain %s", path);
return NULL; return NULL;
} }
@ -585,42 +609,23 @@ int git_index_entry__cmp_icase(const void *a, const void *b)
return strcasecmp(entry_a->path, entry_b->path); return strcasecmp(entry_a->path, entry_b->path);
} }
static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path) static int index_entry_init(
git_index_entry **entry_out, git_index *index, const char *rel_path)
{ {
int error = 0;
git_index_entry *entry = NULL; git_index_entry *entry = NULL;
struct stat st; struct stat st;
git_oid oid; git_oid oid;
const char *workdir;
git_buf full_path = GIT_BUF_INIT;
int error;
if (INDEX_OWNER(index) == NULL) if (INDEX_OWNER(index) == NULL)
return create_index_error(-1, return create_index_error(-1,
"Could not initialize index entry. " "Could not initialize index entry. "
"Index is not backed up by an existing repository."); "Index is not backed up by an existing repository.");
workdir = git_repository_workdir(INDEX_OWNER(index)); /* write the blob to disk and get the oid and stat info */
error = git_blob__create_from_paths(
if (!workdir) &oid, &st, INDEX_OWNER(index), NULL, rel_path, 0, true);
return create_index_error(GIT_EBAREREPO, if (error < 0)
"Could not initialize index entry. Repository is bare");
if ((error = git_buf_joinpath(&full_path, workdir, rel_path)) < 0)
return error;
if ((error = git_path_lstat(full_path.ptr, &st)) < 0) {
git_buf_free(&full_path);
return error;
}
git_buf_free(&full_path); /* done with full path */
/* There is no need to validate the rel_path here, since it will be
* immediately validated by the call to git_blob_create_fromfile.
*/
/* write the blob to disk and get the oid */
if ((error = git_blob_create_fromworkdir(&oid, INDEX_OWNER(index), rel_path)) < 0)
return error; return error;
entry = git__calloc(1, sizeof(git_index_entry)); entry = git__calloc(1, sizeof(git_index_entry));
@ -668,15 +673,6 @@ static int index_entry_reuc_init(git_index_reuc_entry **reuc_out,
return 0; return 0;
} }
static void index_entry_reuc_free(git_index_reuc_entry *reuc)
{
if (!reuc)
return;
git__free(reuc->path);
git__free(reuc);
}
static git_index_entry *index_entry_dup(const git_index_entry *source_entry) static git_index_entry *index_entry_dup(const git_index_entry *source_entry)
{ {
git_index_entry *entry; git_index_entry *entry;
@ -695,14 +691,6 @@ static git_index_entry *index_entry_dup(const git_index_entry *source_entry)
return entry; return entry;
} }
static void index_entry_free(git_index_entry *entry)
{
if (!entry)
return;
git__free(entry->path);
git__free(entry);
}
static int index_insert(git_index *index, git_index_entry *entry, int replace) static int index_insert(git_index *index, git_index_entry *entry, int replace)
{ {
size_t path_length, position; size_t path_length, position;
@ -721,7 +709,8 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace)
entry->flags |= GIT_IDXENTRY_NAMEMASK; entry->flags |= GIT_IDXENTRY_NAMEMASK;
/* look if an entry with this path already exists */ /* look if an entry with this path already exists */
if (!index_find(&position, index, entry->path, GIT_IDXENTRY_STAGE(entry))) { if (!git_index__find(
&position, index, entry->path, GIT_IDXENTRY_STAGE(entry))) {
existing = (git_index_entry **)&index->entries.contents[position]; existing = (git_index_entry **)&index->entries.contents[position];
/* update filemode to existing values if stat is not trusted */ /* update filemode to existing values if stat is not trusted */
@ -734,8 +723,9 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace)
if (!replace || !existing) if (!replace || !existing)
return git_vector_insert(&index->entries, entry); return git_vector_insert(&index->entries, entry);
/* exists, replace it */ /* exists, replace it (preserving name from existing entry) */
git__free((*existing)->path); git__free(entry->path);
entry->path = (*existing)->path;
git__free(*existing); git__free(*existing);
*existing = entry; *existing = entry;
@ -832,7 +822,7 @@ int git_index_remove(git_index *index, const char *path, int stage)
git_vector_sort(&index->entries); git_vector_sort(&index->entries);
if (index_find(&position, index, path, stage) < 0) { if (git_index__find(&position, index, path, stage) < 0) {
giterr_set(GITERR_INDEX, "Index does not contain %s at stage %d", giterr_set(GITERR_INDEX, "Index does not contain %s at stage %d",
path, stage); path, stage);
return GIT_ENOTFOUND; return GIT_ENOTFOUND;
@ -888,7 +878,8 @@ int git_index_remove_directory(git_index *index, const char *dir, int stage)
return error; return error;
} }
static int index_find(size_t *at_pos, git_index *index, const char *path, int stage) int git_index__find(
size_t *at_pos, git_index *index, const char *path, int stage)
{ {
struct entry_srch_key srch_key; struct entry_srch_key srch_key;
@ -897,7 +888,8 @@ static int index_find(size_t *at_pos, git_index *index, const char *path, int st
srch_key.path = path; srch_key.path = path;
srch_key.stage = stage; srch_key.stage = stage;
return git_vector_bsearch2(at_pos, &index->entries, index->entries_search, &srch_key); return git_vector_bsearch2(
at_pos, &index->entries, index->entries_search, &srch_key);
} }
int git_index_find(size_t *at_pos, git_index *index, const char *path) int git_index_find(size_t *at_pos, git_index *index, const char *path)
@ -1354,14 +1346,11 @@ int git_index_reuc_remove(git_index *index, size_t position)
void git_index_reuc_clear(git_index *index) void git_index_reuc_clear(git_index *index)
{ {
size_t i; size_t i;
git_index_reuc_entry *reuc;
assert(index); assert(index);
git_vector_foreach(&index->reuc, i, reuc) { for (i = 0; i < index->reuc.length; ++i)
git__free(reuc->path); index_entry_reuc_free(git__swap(index->reuc.contents[i], NULL));
git__free(reuc);
}
git_vector_clear(&index->reuc); git_vector_clear(&index->reuc);
} }
@ -1386,7 +1375,7 @@ static int read_reuc(git_index *index, const char *buffer, size_t size)
while (size) { while (size) {
git_index_reuc_entry *lost; git_index_reuc_entry *lost;
len = strlen(buffer) + 1; len = p_strnlen(buffer, size) + 1;
if (size <= len) if (size <= len)
return index_error_invalid("reading reuc entries"); return index_error_invalid("reading reuc entries");
@ -1406,14 +1395,18 @@ static int read_reuc(git_index *index, const char *buffer, size_t size)
if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 || if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 ||
!endptr || endptr == buffer || *endptr || !endptr || endptr == buffer || *endptr ||
(unsigned)tmp > UINT_MAX) (unsigned)tmp > UINT_MAX) {
index_entry_reuc_free(lost);
return index_error_invalid("reading reuc entry stage"); return index_error_invalid("reading reuc entry stage");
}
lost->mode[i] = tmp; lost->mode[i] = tmp;
len = (endptr + 1) - buffer; len = (endptr + 1) - buffer;
if (size <= len) if (size <= len) {
index_entry_reuc_free(lost);
return index_error_invalid("reading reuc entry stage"); return index_error_invalid("reading reuc entry stage");
}
size -= len; size -= len;
buffer += len; buffer += len;
@ -1423,8 +1416,10 @@ static int read_reuc(git_index *index, const char *buffer, size_t size)
for (i = 0; i < 3; i++) { for (i = 0; i < 3; i++) {
if (!lost->mode[i]) if (!lost->mode[i])
continue; continue;
if (size < 20) if (size < 20) {
index_entry_reuc_free(lost);
return index_error_invalid("reading reuc entry oid"); return index_error_invalid("reading reuc entry oid");
}
git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer); git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer);
size -= 20; size -= 20;
@ -1453,7 +1448,7 @@ static int read_conflict_names(git_index *index, const char *buffer, size_t size
return -1; return -1;
#define read_conflict_name(ptr) \ #define read_conflict_name(ptr) \
len = strlen(buffer) + 1; \ len = p_strnlen(buffer, size) + 1; \
if (size < len) \ if (size < len) \
return index_error_invalid("reading conflict name entries"); \ return index_error_invalid("reading conflict name entries"); \
\ \
@ -1580,7 +1575,8 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer
total_size = dest.extension_size + sizeof(struct index_extension); total_size = dest.extension_size + sizeof(struct index_extension);
if (buffer_size < total_size || if (dest.extension_size > total_size ||
buffer_size < total_size ||
buffer_size - total_size < INDEX_FOOTER_SIZE) buffer_size - total_size < INDEX_FOOTER_SIZE)
return 0; return 0;
@ -1955,8 +1951,9 @@ int git_index_entry_stage(const git_index_entry *entry)
} }
typedef struct read_tree_data { typedef struct read_tree_data {
git_index *index;
git_vector *old_entries; git_vector *old_entries;
git_vector *new_entries;
git_vector_cmp entries_search;
} read_tree_data; } read_tree_data;
static int read_tree_cb( static int read_tree_cb(
@ -1987,7 +1984,7 @@ static int read_tree_cb(
skey.stage = 0; skey.stage = 0;
if (!git_vector_bsearch2( if (!git_vector_bsearch2(
&pos, data->old_entries, data->index->entries_search, &skey) && &pos, data->old_entries, data->entries_search, &skey) &&
(old_entry = git_vector_get(data->old_entries, pos)) != NULL && (old_entry = git_vector_get(data->old_entries, pos)) != NULL &&
entry->mode == old_entry->mode && entry->mode == old_entry->mode &&
git_oid_equal(&entry->oid, &old_entry->oid)) git_oid_equal(&entry->oid, &old_entry->oid))
@ -2005,7 +2002,7 @@ static int read_tree_cb(
entry->path = git_buf_detach(&path); entry->path = git_buf_detach(&path);
git_buf_free(&path); git_buf_free(&path);
if (git_vector_insert(&data->index->entries, entry) < 0) { if (git_vector_insert(data->new_entries, entry) < 0) {
index_entry_free(entry); index_entry_free(entry);
return -1; return -1;
} }
@ -2019,22 +2016,22 @@ int git_index_read_tree(git_index *index, const git_tree *tree)
git_vector entries = GIT_VECTOR_INIT; git_vector entries = GIT_VECTOR_INIT;
read_tree_data data; read_tree_data data;
git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */
data.old_entries = &index->entries;
data.new_entries = &entries;
data.entries_search = index->entries_search;
git_vector_sort(&index->entries); git_vector_sort(&index->entries);
entries._cmp = index->entries._cmp;
git_vector_swap(&entries, &index->entries);
git_index_clear(index);
data.index = index;
data.old_entries = &entries;
error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data); error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data);
index_entries_free(&entries); git_vector_sort(&entries);
git_vector_free(&entries);
git_vector_sort(&index->entries); git_index_clear(index);
git_vector_swap(&entries, &index->entries);
git_vector_free(&entries);
return error; return error;
} }
@ -2043,3 +2040,219 @@ git_repository *git_index_owner(const git_index *index)
{ {
return INDEX_OWNER(index); return INDEX_OWNER(index);
} }
int git_index_add_all(
git_index *index,
const git_strarray *paths,
unsigned int flags,
git_index_matched_path_cb cb,
void *payload)
{
int error;
git_repository *repo;
git_iterator *wditer = NULL;
const git_index_entry *wd = NULL;
git_index_entry *entry;
git_pathspec ps;
const char *match;
size_t existing;
bool no_fnmatch = (flags & GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH) != 0;
int ignorecase;
git_oid blobid;
assert(index);
if (INDEX_OWNER(index) == NULL)
return create_index_error(-1,
"Could not add paths to index. "
"Index is not backed up by an existing repository.");
repo = INDEX_OWNER(index);
if ((error = git_repository__ensure_not_bare(repo, "index add all")) < 0)
return error;
if (git_repository__cvar(&ignorecase, repo, GIT_CVAR_IGNORECASE) < 0)
return -1;
if ((error = git_pathspec__init(&ps, paths)) < 0)
return error;
/* optionally check that pathspec doesn't mention any ignored files */
if ((flags & GIT_INDEX_ADD_CHECK_PATHSPEC) != 0 &&
(flags & GIT_INDEX_ADD_FORCE) == 0 &&
(error = git_ignore__check_pathspec_for_exact_ignores(
repo, &ps.pathspec, no_fnmatch)) < 0)
goto cleanup;
if ((error = git_iterator_for_workdir(
&wditer, repo, 0, ps.prefix, ps.prefix)) < 0)
goto cleanup;
while (!(error = git_iterator_advance(&wd, wditer))) {
/* check if path actually matches */
if (!git_pathspec__match(
&ps.pathspec, wd->path, no_fnmatch, ignorecase, &match, NULL))
continue;
/* skip ignored items that are not already in the index */
if ((flags & GIT_INDEX_ADD_FORCE) == 0 &&
git_iterator_current_is_ignored(wditer) &&
git_index__find(&existing, index, wd->path, 0) < 0)
continue;
/* issue notification callback if requested */
if (cb && (error = cb(wd->path, match, payload)) != 0) {
if (error > 0) /* return > 0 means skip this one */
continue;
if (error < 0) { /* return < 0 means abort */
giterr_clear();
error = GIT_EUSER;
break;
}
}
/* TODO: Should we check if the file on disk is already an exact
* match to the file in the index and skip this work if it is?
*/
/* write the blob to disk and get the oid */
if ((error = git_blob_create_fromworkdir(&blobid, repo, wd->path)) < 0)
break;
/* make the new entry to insert */
if ((entry = index_entry_dup(wd)) == NULL) {
error = -1;
break;
}
entry->oid = blobid;
/* add working directory item to index */
if ((error = index_insert(index, entry, 1)) < 0) {
index_entry_free(entry);
break;
}
git_tree_cache_invalidate_path(index->tree, wd->path);
/* add implies conflict resolved, move conflict entries to REUC */
if ((error = index_conflict_to_reuc(index, wd->path)) < 0) {
if (error != GIT_ENOTFOUND)
break;
giterr_clear();
}
}
if (error == GIT_ITEROVER)
error = 0;
cleanup:
git_iterator_free(wditer);
git_pathspec__clear(&ps);
return error;
}
enum {
INDEX_ACTION_NONE = 0,
INDEX_ACTION_UPDATE = 1,
INDEX_ACTION_REMOVE = 2,
};
static int index_apply_to_all(
git_index *index,
int action,
const git_strarray *paths,
git_index_matched_path_cb cb,
void *payload)
{
int error = 0;
size_t i;
git_pathspec ps;
const char *match;
git_buf path = GIT_BUF_INIT;
assert(index);
if ((error = git_pathspec__init(&ps, paths)) < 0)
return error;
git_vector_sort(&index->entries);
for (i = 0; !error && i < index->entries.length; ++i) {
git_index_entry *entry = git_vector_get(&index->entries, i);
/* check if path actually matches */
if (!git_pathspec__match(
&ps.pathspec, entry->path, false, index->ignore_case,
&match, NULL))
continue;
/* issue notification callback if requested */
if (cb && (error = cb(entry->path, match, payload)) != 0) {
if (error > 0) { /* return > 0 means skip this one */
error = 0;
continue;
}
if (error < 0) { /* return < 0 means abort */
giterr_clear();
error = GIT_EUSER;
break;
}
}
/* index manipulation may alter entry, so don't depend on it */
if ((error = git_buf_sets(&path, entry->path)) < 0)
break;
switch (action) {
case INDEX_ACTION_NONE:
break;
case INDEX_ACTION_UPDATE:
error = git_index_add_bypath(index, path.ptr);
if (error == GIT_ENOTFOUND) {
giterr_clear();
error = git_index_remove_bypath(index, path.ptr);
if (!error) /* back up foreach if we removed this */
i--;
}
break;
case INDEX_ACTION_REMOVE:
if (!(error = git_index_remove_bypath(index, path.ptr)))
i--; /* back up foreach if we removed this */
break;
default:
giterr_set(GITERR_INVALID, "Unknown index action %d", action);
error = -1;
break;
}
}
git_buf_free(&path);
git_pathspec__clear(&ps);
return error;
}
int git_index_remove_all(
git_index *index,
const git_strarray *pathspec,
git_index_matched_path_cb cb,
void *payload)
{
return index_apply_to_all(
index, INDEX_ACTION_REMOVE, pathspec, cb, payload);
}
int git_index_update_all(
git_index *index,
const git_strarray *pathspec,
git_index_matched_path_cb cb,
void *payload)
{
return index_apply_to_all(
index, INDEX_ACTION_UPDATE, pathspec, cb, payload);
}

View File

@ -47,13 +47,17 @@ struct git_index_conflict_iterator {
size_t cur; size_t cur;
}; };
extern void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st); extern void git_index_entry__init_from_stat(
git_index_entry *entry, struct stat *st);
extern size_t git_index__prefix_position(git_index *index, const char *path); extern size_t git_index__prefix_position(git_index *index, const char *path);
extern int git_index_entry__cmp(const void *a, const void *b); extern int git_index_entry__cmp(const void *a, const void *b);
extern int git_index_entry__cmp_icase(const void *a, const void *b); extern int git_index_entry__cmp_icase(const void *a, const void *b);
extern int git_index__find(
size_t *at_pos, git_index *index, const char *path, int stage);
extern void git_index__set_ignore_case(git_index *index, bool ignore_case); extern void git_index__set_ignore_case(git_index *index, bool ignore_case);
#endif #endif

View File

@ -325,7 +325,7 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent
/* FIXME: Parse the object instead of hashing it */ /* FIXME: Parse the object instead of hashing it */
if (git_odb__hashobj(&oid, obj) < 0) { if (git_odb__hashobj(&oid, obj) < 0) {
giterr_set(GITERR_INDEXER, "Failed to hash object"); giterr_set(GITERR_INDEXER, "Failed to hash object");
return -1; goto on_error;
} }
pentry = git__calloc(1, sizeof(struct git_pack_entry)); pentry = git__calloc(1, sizeof(struct git_pack_entry));
@ -602,7 +602,7 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *
git_vector_sort(&idx->objects); git_vector_sort(&idx->objects);
git_buf_sets(&filename, idx->pack->pack_name); git_buf_sets(&filename, idx->pack->pack_name);
git_buf_truncate(&filename, filename.size - strlen("pack")); git_buf_shorten(&filename, strlen("pack"));
git_buf_puts(&filename, "idx"); git_buf_puts(&filename, "idx");
if (git_buf_oom(&filename)) if (git_buf_oom(&filename))
return -1; return -1;

View File

@ -1321,9 +1321,10 @@ static void workdir_iterator__free(git_iterator *self)
git_ignore__free(&wi->ignores); git_ignore__free(&wi->ignores);
} }
int git_iterator_for_workdir( int git_iterator_for_workdir_ext(
git_iterator **out, git_iterator **out,
git_repository *repo, git_repository *repo,
const char *repo_workdir,
git_iterator_flag_t flags, git_iterator_flag_t flags,
const char *start, const char *start,
const char *end) const char *end)
@ -1331,8 +1332,11 @@ int git_iterator_for_workdir(
int error; int error;
workdir_iterator *wi; workdir_iterator *wi;
if (!repo_workdir) {
if (git_repository__ensure_not_bare(repo, "scan working directory") < 0) if (git_repository__ensure_not_bare(repo, "scan working directory") < 0)
return GIT_EBAREREPO; return GIT_EBAREREPO;
repo_workdir = git_repository_workdir(repo);
}
/* initialize as an fs iterator then do overrides */ /* initialize as an fs iterator then do overrides */
wi = git__calloc(1, sizeof(workdir_iterator)); wi = git__calloc(1, sizeof(workdir_iterator));
@ -1346,13 +1350,13 @@ int git_iterator_for_workdir(
wi->fi.update_entry_cb = workdir_iterator__update_entry; wi->fi.update_entry_cb = workdir_iterator__update_entry;
if ((error = iterator__update_ignore_case((git_iterator *)wi, flags)) < 0 || if ((error = iterator__update_ignore_case((git_iterator *)wi, flags)) < 0 ||
(error = git_ignore__for_path(repo, "", &wi->ignores)) < 0) (error = git_ignore__for_path(repo, ".gitignore", &wi->ignores)) < 0)
{ {
git_iterator_free((git_iterator *)wi); git_iterator_free((git_iterator *)wi);
return error; return error;
} }
return fs_iterator__initialize(out, &wi->fi, git_repository_workdir(repo)); return fs_iterator__initialize(out, &wi->fi, repo_workdir);
} }

Some files were not shown because too many files have changed in this diff Show More