Merge branch 'development'

This commit is contained in:
Vicent Marti 2013-11-20 12:54:24 +01:00
commit 4b0a36e881
2313 changed files with 30347 additions and 9823 deletions

6
.gitignore vendored
View File

@ -1,6 +1,6 @@
/tests-clar/clar.suite
/tests-clar/clar.suite.rule
/tests-clar/.clarcache
/tests/clar.suite
/tests/clar.suite.rule
/tests/.clarcache
/apidocs
/trash-*.exe
/libgit2.pc

View File

@ -16,3 +16,6 @@ Xavier L. <xavier.l@afrosoft.tk> <xavier.l@afrosoft.tk>
Sascha Cunz <sascha@babbelbox.org> <Sascha@BabbelBox.org>
Authmillenon <authmillenon@googlemail.com> <martin@ucsmail.de>
Authmillenon <authmillenon@googlemail.com> <authmillenon@googlemail.com>
Edward Thomson <ethomson@microsoft.com> <ethomson@edwardthomson.com>
J. David Ibáñez <jdavid.ibp@gmail.com> <jdavid@itaapy.com>
Russell Belfer <rb@github.com> <arrbee@arrbee.com>

View File

@ -1,7 +1,6 @@
# Travis-CI Build for libgit2
# see travis-ci.org for details
# As CMake is not officially supported we use erlang VMs
language: c
compiler:
@ -18,26 +17,18 @@ matrix:
- compiler: i586-mingw32msvc-gcc
env: OPTIONS="-DBUILD_CLAR=OFF -DWIN32=ON -DMINGW=ON"
# Make sure CMake is installed
install:
- sudo apt-get update >/dev/null
- sudo apt-get -q install cmake valgrind
- sudo apt-get -qq update
- sudo apt-get -qq install cmake libssh2-1-dev openssh-client openssh-server
# Run the Build script
# Run the Build script and tests
script:
- mkdir _temp
- 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 .
- script/cibuild.sh
# Run Tests
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
branches:

View File

@ -68,5 +68,6 @@ Sven Strickroth
Tim Branyen
Tim Clem
Tim Harder
Torsten Bögershausen
Trent Mick
Vicent Marti

View File

@ -29,23 +29,42 @@ OPTION( PROFILE "Generate profiling information" OFF )
OPTION( ENABLE_TRACE "Enables tracing support" OFF )
OPTION( LIBGIT2_FILENAME "Name of the produced binary" OFF )
OPTION( ANDROID "Build for android NDK" OFF )
OPTION( USE_ICONV "Link with and use iconv library" OFF )
OPTION( USE_SSH "Link with libssh to enable SSH support" ON )
IF(APPLE)
SET( USE_ICONV ON )
ENDIF()
IF(MSVC)
# This option is only availalbe when building with MSVC. By default,
# libgit2 is build using the stdcall calling convention, as that's what
# the CLR expects by default and how the Windows API is built.
# This option is only available when building with MSVC. By default, libgit2
# is build using the cdecl calling convention, which is useful if you're
# 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
# have to either:
# - Add /Gz to the compiler options of _your_ program / library.
# - Turn this off by invoking CMake with the "-DSTDCALL=Off" argument.
#
OPTION( STDCALL "Build libgit2 with the __stdcall convention" ON )
# If you are writing a CLR program and want to link to libgit2, you'll want
# to turn this on by invoking CMake with the "-DSTDCALL=ON" argument.
OPTION( STDCALL "Build libgit2 with the __stdcall convention" OFF )
# This option must match the settings used in your program, in particular if you
# are linking statically
OPTION( STATIC_CRT "Link the static CRT libraries" ON )
# By default, libgit2 is built with WinHTTP. To use the built-in
# HTTP transport, invoke CMake with the "-DWINHTTP=OFF" argument.
OPTION( WINHTTP "Use Win32 WinHTTP routines" ON )
ENDIF()
# This variable will contain the libraries we need to put into
# libgit2.pc's Requires.private. That is, what we're linking to or
# what someone who's statically linking us needs to link to.
SET(LIBGIT2_PC_REQUIRES "")
# This will be set later if we use the system's http-parser library or
# use iconv (OSX) and will be written to the Libs.private field in the
# pc file.
SET(LIBGIT2_PC_LIBS "")
# Installation paths
#
SET(BIN_INSTALL_DIR bin CACHE PATH "Where to install binaries to.")
@ -57,7 +76,18 @@ FUNCTION(TARGET_OS_LIBRARIES target)
TARGET_LINK_LIBRARIES(${target} ws2_32)
ELSEIF(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)")
TARGET_LINK_LIBRARIES(${target} socket nsl)
ENDIF ()
SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -lsocket -lnsl" PARENT_SCOPE)
ELSEIF(CMAKE_SYSTEM_NAME MATCHES "Linux")
TARGET_LINK_LIBRARIES(${target} rt)
SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -lrt" PARENT_SCOPE)
ENDIF()
IF(USE_ICONV)
TARGET_LINK_LIBRARIES(${target} iconv)
ADD_DEFINITIONS(-DGIT_USE_ICONV)
SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -liconv" PARENT_SCOPE)
ENDIF()
IF(THREADSAFE)
TARGET_LINK_LIBRARIES(${target} ${CMAKE_THREAD_LIBS_INIT})
ENDIF()
@ -67,7 +97,7 @@ ENDFUNCTION()
# explorer does. This is esp. useful with the libgit2_clar project, were
# usually 2 or more files share the same name. Sadly, this file grouping
# is a per-directory option in cmake and not per-target, resulting in
# empty virtual folders "tests-clar" for the git2.dll
# empty virtual folders "tests" for the git2.dll
FUNCTION(MSVC_SPLIT_SOURCES target)
IF(MSVC_IDE)
GET_TARGET_PROPERTY(sources ${target} SOURCES)
@ -96,8 +126,10 @@ SET(LIBGIT2_VERSION_STRING "${LIBGIT2_VERSION_MAJOR}.${LIBGIT2_VERSION_MINOR}.${
# Find required dependencies
INCLUDE_DIRECTORIES(src include)
IF (WIN32 AND NOT MINGW)
IF (WIN32 AND WINHTTP AND NOT MINGW)
ADD_DEFINITIONS(-DGIT_WINHTTP)
INCLUDE_DIRECTORIES(deps/http-parser)
FILE(GLOB SRC_HTTP deps/http-parser/*.c deps/http-parser/*.h)
ELSE ()
IF (NOT AMIGA)
FIND_PACKAGE(OpenSSL)
@ -107,10 +139,11 @@ ELSE ()
IF (HTTP_PARSER_FOUND AND HTTP_PARSER_VERSION_MAJOR EQUAL 2)
INCLUDE_DIRECTORIES(${HTTP_PARSER_INCLUDE_DIRS})
LINK_LIBRARIES(${HTTP_PARSER_LIBRARIES})
SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -lhttp_parser")
ELSE()
MESSAGE("http-parser was not found or is too old; using bundled 3rd-party sources.")
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()
@ -120,6 +153,7 @@ IF (WIN32 AND NOT MINGW AND NOT SHA1_TYPE STREQUAL "builtin")
FILE(GLOB SRC_SHA1 src/hash/hash_win32.c)
ELSEIF (OPENSSL_FOUND AND NOT SHA1_TYPE STREQUAL "builtin")
ADD_DEFINITIONS(-DOPENSSL_SHA1)
SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} openssl")
ELSE()
FILE(GLOB SRC_SHA1 src/hash/hash_generic.c)
ENDIF()
@ -130,7 +164,7 @@ IF (ENABLE_TRACE STREQUAL "ON")
ENDIF()
# Include POSIX regex when it is required
IF(WIN32 OR AMIGA)
IF(WIN32 OR AMIGA OR ANDROID)
INCLUDE_DIRECTORIES(deps/regex)
SET(SRC_REGEX deps/regex/regex.c)
ENDIF()
@ -142,24 +176,31 @@ FIND_PACKAGE(ZLIB QUIET)
IF (ZLIB_FOUND)
INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS})
LINK_LIBRARIES(${ZLIB_LIBRARIES})
IF(APPLE)
SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -lz")
ELSE()
SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} zlib")
ENDIF()
# Fake the message CMake would have shown
MESSAGE("-- Found zlib: ${ZLIB_LIBRARY}")
ELSE()
MESSAGE( "zlib was not found; using bundled 3rd-party sources." )
INCLUDE_DIRECTORIES(deps/zlib)
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()
IF(NOT LIBSSH2_LIBRARY)
IF (USE_SSH AND NOT MINGW)
FIND_PACKAGE(LIBSSH2 QUIET)
ENDIF()
IF (LIBSSH2_FOUND)
ADD_DEFINITIONS(-DGIT_SSH)
INCLUDE_DIRECTORIES(${LIBSSH2_INCLUDE_DIR})
SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} libssh2")
SET(SSH_LIBRARIES ${LIBSSH2_LIBRARIES})
ENDIF()
# Platform specific compilation flags
IF (MSVC)
@ -285,19 +326,19 @@ ENDIF()
ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64)
# 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
IF (WIN32 AND NOT CYGWIN)
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)
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()
FILE(GLOB SRC_OS src/unix/*.c)
FILE(GLOB SRC_OS src/unix/*.c src/unix/*.h)
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
IF (CMAKE_SIZEOF_VOID_P EQUAL 8)
@ -309,7 +350,7 @@ ELSE()
ENDIF()
# 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 ${SSH_LIBRARIES})
TARGET_OS_LIBRARIES(git2)
@ -352,19 +393,19 @@ INSTALL(FILES include/git2.h DESTINATION ${INCLUDE_INSTALL_DIR} )
IF (BUILD_CLAR)
FIND_PACKAGE(PythonInterp REQUIRED)
SET(CLAR_FIXTURES "${CMAKE_CURRENT_SOURCE_DIR}/tests-clar/resources/")
SET(CLAR_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tests-clar")
SET(CLAR_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tests-clar/resources" CACHE PATH "Path to test resources.")
SET(CLAR_FIXTURES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources/")
SET(CLAR_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tests")
SET(CLAR_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources" CACHE PATH "Path to test resources.")
ADD_DEFINITIONS(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\")
ADD_DEFINITIONS(-DCLAR_RESOURCES=\"${TEST_RESOURCES}\")
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")
ADD_CUSTOM_COMMAND(
OUTPUT ${CLAR_PATH}/clar.suite
COMMAND ${PYTHON_EXECUTABLE} generate.py -f -xonline .
COMMAND ${PYTHON_EXECUTABLE} generate.py -f -xonline -xstress .
DEPENDS ${SRC_TEST}
WORKING_DIRECTORY ${CLAR_PATH}
)
@ -373,7 +414,7 @@ IF (BUILD_CLAR)
${CLAR_PATH}/clar.c
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 ${SSH_LIBRARIES})
@ -409,23 +450,5 @@ IF (TAGS)
ENDIF ()
IF (BUILD_EXAMPLES)
FILE(GLOB_RECURSE EXAMPLE_SRC examples/network/*.c)
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)
ADD_SUBDIRECTORY(examples)
ENDIF ()

View File

@ -3,6 +3,12 @@
We're making it easy to do interesting things with git, and we'd love to have
your help.
## Licensing
By contributing to libgit2, you agree to release your contribution under the terms of the license.
For code under `examples`, this is governed by the [CC0 Public Domain Dedication](examples/COPYING).
All other code is released under the [GPL v2 with linking exception](COPYING).
## Discussion & Chat
We hang out in the #libgit2 channel on irc.freenode.net.
@ -48,6 +54,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
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
`libgit2` is licensed under the terms of the GPL v2 with a linking
@ -57,14 +69,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
explicit permission of the author. Check the
[`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
you've obtained their consent.
file for authors who have already consented.
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,
MIT and BSD (3-clause) licenses are typically no problem. Apache 2.0
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
`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
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,26 +1,44 @@
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
AR=ar cq
RANLIB=ranlib
AR=$(PREFIX)ar cq
RANLIB=$(PREFIX)ranlib
LIBNAME=libgit2.a
ifeq ($(PLATFORM),Msys)
ifeq ($(MINGW),1)
CC=gcc
else
CC=cc
endif
CC:=$(PREFIX)$(CC)
INCLUDES= -I. -Isrc -Iinclude -Ideps/http-parser -Ideps/zlib
DEFINES= $(INCLUDES) -DNO_VIZ -DSTDC -DNO_GZIP -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE $(EXTRA_DEFINES)
CFLAGS= -g $(DEFINES) -Wall -Wextra -O2 $(EXTRA_CFLAGS)
CFLAGS= -g $(DEFINES) -Wall -Wextra -Wno-missing-field-initializers -O2 $(EXTRA_CFLAGS)
SRCS = $(wildcard src/*.c) $(wildcard src/transports/*.c) $(wildcard src/xdiff/*.c) $(wildcard deps/http-parser/*.c) $(wildcard deps/zlib/*.c) src/hash/hash_generic.c
ifeq ($(PLATFORM),Msys)
ifeq ($(MINGW),1)
SRCS += $(wildcard src/win32/*.c) $(wildcard src/compat/*.c) deps/regex/regex.c
INCLUDES += -Ideps/regex
DEFINES += -DWIN32 -D_WIN32_WINNT=0x0501
DEFINES += -DWIN32 -D_WIN32_WINNT=0x0501 -D__USE_MINGW_ANSI_STDIO=1
else
SRCS += $(wildcard src/unix/*.c)
CFLAGS += -fPIC

View File

@ -1,30 +1,36 @@
libgit2 - the Git linkable library
======================
==================================
[![Build Status](https://secure.travis-ci.org/libgit2/libgit2.png?branch=development)](http://travis-ci.org/libgit2/libgit2)
libgit2 is a portable, pure C implementation of the Git core methods provided as a
`libgit2` is a portable, pure C implementation of the Git core methods provided as a
re-entrant linkable library with a solid API, allowing you to write native
speed custom Git applications in any language with bindings.
libgit2 is licensed under a **very permissive license** (GPLv2 with a special Linking Exception).
This basically means that you can link it (unmodified) with any kind of software without having to
release its source code.
`libgit2` is licensed under a **very permissive license** (GPLv2 with a special
Linking Exception). This basically means that you can link it (unmodified)
with any kind of software without having to release its source code.
Additionally, the example code has been released to the public domain (see the
[separate license](examples/COPYING) for more information).
* 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: [libgit2.github.com](http://libgit2.github.com)
* StackOverflow Tag: [libgit2](http://stackoverflow.com/questions/tagged/libgit2)
* Issues: [GitHub Issues](https://github.com/libgit2/libgit2/issues) (Right here!)
* API documentation: <http://libgit2.github.com/libgit2>
* IRC: #libgit2 on irc.freenode.net.
* IRC: [#libgit2](irc://irc.freenode.net/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
==================================
==============
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
* abstracted ODB backend system
@ -39,15 +45,26 @@ libgit2 is already very usable.
* descriptive and detailed error messages
* ...and more (over 175 different API calls)
Optional dependencies
=====================
While the library provides git functionality without the need for
dependencies, it can make use of a few libraries to add to it:
- pthreads (non-Windows) to enable threadsafe access as well as multi-threaded pack generation
- OpenSSL (non-Windows) to talk over HTTPS and provide the SHA-1 functions
- LibSSH2 to enable the ssh transport
- iconv (OSX) to handle the HFS+ path encoding peculiarities
Building libgit2 - Using CMake
==============================
libgit2 builds cleanly on most platforms without any external dependencies.
`libgit2` builds cleanly on most platforms without any external dependencies.
Under Unix-like systems, like Linux, \*BSD and Mac OS X, libgit2 expects `pthreads` to be available;
they should be installed by default on all systems. Under Windows, libgit2 uses the native Windows API
for threading.
The libgit2 library is built using CMake 2.6+ (<http://www.cmake.org>) on all platforms.
The `libgit2` library is built using `CMake 2.6+` (<http://www.cmake.org>) on all platforms.
On most systems you can build the library using the following commands
@ -104,6 +121,28 @@ See [the wiki]
(https://github.com/libgit2/libgit2/wiki/Building-libgit2-on-Windows)
for more detailed instructions.
Android
-------
Extract toolchain from NDK using, `make-standalone-toolchain.sh` script.
Optionally, crosscompile and install OpenSSL inside of it. Then create CMake
toolchain file that configures paths to your crosscompiler (substitute `{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
==================================
@ -118,9 +157,9 @@ Here are the bindings to libgit2 that are currently available:
* Delphi
* GitForDelphi <https://github.com/libgit2/GitForDelphi>
* Erlang
* Geef <https://github.com/schacon/geef>
* Geef <https://github.com/carlosmn/geef>
* Go
* go-git <https://github.com/str1ngs/go-git>
* git2go <https://github.com/libgit2/git2go>
* GObject
* libgit2-glib <https://live.gnome.org/Libgit2-glib>
* Haskell
@ -128,8 +167,8 @@ Here are the bindings to libgit2 that are currently available:
* Lua
* luagit2 <https://github.com/libgit2/luagit2>
* .NET
* libgit2net, low level bindings <https://github.com/txdv/libgit2net>
* libgit2sharp <https://github.com/libgit2/libgit2sharp>
* libgit2net, low level bindings superseded by libgit2sharp <https://github.com/txdv/libgit2net>
* Node.js
* node-gitteh <https://github.com/libgit2/node-gitteh>
* nodegit <https://github.com/tbranyen/nodegit>
@ -161,9 +200,9 @@ Check the [contribution guidelines](CONTRIBUTING.md).
License
==================================
libgit2 is under GPL2 **with linking exemption**. This means you
can link to the library with any program, commercial, open source or
other. However, you cannot modify libgit2 and distribute it without
`libgit2` is under GPL2 **with linking exemption**. This means you
can link to and use the library from any program, proprietary or open source; paid
or gratis. However, you cannot modify libgit2 and distribute it without
supplying the source.
See the COPYING file for the full license text.

View File

@ -71,19 +71,19 @@ Key
Diff with 2 non-workdir iterators
---------------------------------
Old New
--- ---
0 x x - nothing
1 x B1 - added blob
2 x T1 - added tree
3 B1 x - removed blob
4 B1 B1 - unmodified blob
5 B1 B2 - modified blob
6 B1 T1 - typechange blob -> tree
7 T1 x - removed tree
8 T1 B1 - typechange tree -> blob
9 T1 T1 - unmodified tree
10 T1 T2 - modified tree (implies modified/added/removed blob inside)
| | Old | New | |
|----|-----|-----|------------------------------------------------------------|
| 0 | x | x | nothing |
| 1 | x | B1 | added blob |
| 2 | x | T1 | added tree |
| 3 | B1 | x | removed blob |
| 4 | B1 | B1 | unmodified blob |
| 5 | B1 | B2 | modified blob |
| 6 | B1 | T1 | typechange blob -> tree |
| 7 | T1 | x | removed tree |
| 8 | T1 | B1 | typechange tree -> blob |
| 9 | T1 | T1 | unmodified tree |
| 10 | T1 | T2 | modified tree (implies modified/added/removed blob inside) |
Now, let's make the "New" iterator into a working directory iterator, so
@ -92,23 +92,23 @@ we replace "added" items with either untracked or ignored, like this:
Diff with non-work & workdir iterators
--------------------------------------
Old New-WD
--- ------
0 x x - nothing
1 x B1 - untracked blob
2 x Bi - ignored file
3 x T1 - untracked tree
4 x Ti - ignored tree
5 B1 x - removed blob
6 B1 B1 - unmodified blob
7 B1 B2 - modified blob
8 B1 T1 - typechange blob -> tree
9 B1 Ti - removed blob AND ignored tree as separate items
10 T1 x - removed tree
11 T1 B1 - typechange tree -> blob
12 T1 Bi - removed tree AND ignored blob as separate items
13 T1 T1 - unmodified tree
14 T1 T2 - modified tree (implies modified/added/removed blob inside)
| | Old | New | |
|----|-----|-----|------------------------------------------------------------|
| 0 | x | x | nothing |
| 1 | x | B1 | untracked blob |
| 2 | x | Bi | ignored file |
| 3 | x | T1 | untracked tree |
| 4 | x | Ti | ignored tree |
| 5 | B1 | x | removed blob |
| 6 | B1 | B1 | unmodified blob |
| 7 | B1 | B2 | modified blob |
| 8 | B1 | T1 | typechange blob -> tree |
| 9 | B1 | Ti | removed blob AND ignored tree as separate items |
| 10 | T1 | x | removed tree |
| 11 | T1 | B1 | typechange tree -> blob |
| 12 | T1 | Bi | removed tree AND ignored blob as separate items |
| 13 | T1 | T1 | unmodified tree |
| 14 | T1 | T2 | modified tree (implies modified/added/removed blob inside) |
Note: if there is a corresponding entry in the old tree, then a working
directory item won't be ignored (i.e. no Bi or Ti for tracked items).
@ -122,46 +122,47 @@ Checkout From 3 Iterators (2 not workdir, 1 workdir)
(base == old HEAD; target == what to checkout; actual == working dir)
base target actual/workdir
---- ------ ------
0 x x x - nothing
1 x x B1/Bi/T1/Ti - untracked/ignored blob/tree (SAFE)
2+ x B1 x - add blob (SAFE)
3 x B1 B1 - independently added blob (FORCEABLE-2)
4* x B1 B2/Bi/T1/Ti - add blob with content conflict (FORCEABLE-2)
5+ x T1 x - add tree (SAFE)
6* x T1 B1/Bi - add tree with blob conflict (FORCEABLE-2)
7 x T1 T1/i - independently added tree (SAFE+MISSING)
8 B1 x x - independently deleted blob (SAFE+MISSING)
9- B1 x B1 - delete blob (SAFE)
10- B1 x B2 - delete of modified blob (FORCEABLE-1)
11 B1 x T1/Ti - independently deleted blob AND untrack/ign tree (SAFE+MISSING !!!)
12 B1 B1 x - locally deleted blob (DIRTY || SAFE+CREATE)
13+ B1 B2 x - update to deleted blob (SAFE+MISSING)
14 B1 B1 B1 - unmodified file (SAFE)
15 B1 B1 B2 - locally modified file (DIRTY)
16+ B1 B2 B1 - update unmodified blob (SAFE)
17 B1 B2 B2 - independently updated blob (FORCEABLE-1)
18+ B1 B2 B3 - update to modified blob (FORCEABLE-1)
19 B1 B1 T1/Ti - locally deleted blob AND untrack/ign tree (DIRTY)
20* B1 B2 T1/Ti - update to deleted blob AND untrack/ign tree (F-1)
21+ B1 T1 x - add tree with locally deleted blob (SAFE+MISSING)
22* B1 T1 B1 - add tree AND deleted blob (SAFE)
23* B1 T1 B2 - add tree with delete of modified blob (F-1)
24 B1 T1 T1 - add tree with deleted blob (F-1)
25 T1 x x - independently deleted tree (SAFE+MISSING)
26 T1 x B1/Bi - independently deleted tree AND untrack/ign blob (F-1)
27- T1 x T1 - deleted tree (MAYBE SAFE)
28+ T1 B1 x - deleted tree AND added blob (SAFE+MISSING)
29 T1 B1 B1 - independently typechanged tree -> blob (F-1)
30+ T1 B1 B2 - typechange tree->blob with conflicting blob (F-1)
31* T1 B1 T1/T2 - typechange tree->blob (MAYBE SAFE)
32+ T1 T1 x - restore locally deleted tree (SAFE+MISSING)
33 T1 T1 B1/Bi - locally typechange tree->untrack/ign blob (DIRTY)
34 T1 T1 T1/T2 - unmodified tree (MAYBE SAFE)
35+ T1 T2 x - update locally deleted tree (SAFE+MISSING)
36* T1 T2 B1/Bi - update to tree with typechanged tree->blob conflict (F-1)
37 T1 T2 T1/T2/T3 - update to existing tree (MAYBE SAFE)
| |base | target | actual/workdir | |
|-----|-----|------- |----------------|--------------------------------------------------------------------|
| 0 | x | x | x | nothing |
| 1 | x | x | B1/Bi/T1/Ti | untracked/ignored blob/tree (SAFE) |
| 2+ | x | B1 | x | add blob (SAFE) |
| 3 | x | B1 | B1 | independently added blob (FORCEABLE-2) |
| 4* | x | B1 | B2/Bi/T1/Ti | add blob with content conflict (FORCEABLE-2) |
| 5+ | x | T1 | x | add tree (SAFE) |
| 6* | x | T1 | B1/Bi | add tree with blob conflict (FORCEABLE-2) |
| 7 | x | T1 | T1/i | independently added tree (SAFE+MISSING) |
| 8 | B1 | x | x | independently deleted blob (SAFE+MISSING) |
| 9- | B1 | x | B1 | delete blob (SAFE) |
| 10- | B1 | x | B2 | delete of modified blob (FORCEABLE-1) |
| 11 | B1 | x | T1/Ti | independently deleted blob AND untrack/ign tree (SAFE+MISSING !!!) |
| 12 | B1 | B1 | x | locally deleted blob (DIRTY || SAFE+CREATE) |
| 13+ | B1 | B2 | x | update to deleted blob (SAFE+MISSING) |
| 14 | B1 | B1 | B1 | unmodified file (SAFE) |
| 15 | B1 | B1 | B2 | locally modified file (DIRTY) |
| 16+ | B1 | B2 | B1 | update unmodified blob (SAFE) |
| 17 | B1 | B2 | B2 | independently updated blob (FORCEABLE-1) |
| 18+ | B1 | B2 | B3 | update to modified blob (FORCEABLE-1) |
| 19 | B1 | B1 | T1/Ti | locally deleted blob AND untrack/ign tree (DIRTY) |
| 20* | B1 | B2 | T1/Ti | update to deleted blob AND untrack/ign tree (F-1) |
| 21+ | B1 | T1 | x | add tree with locally deleted blob (SAFE+MISSING) |
| 22* | B1 | T1 | B1 | add tree AND deleted blob (SAFE) |
| 23* | B1 | T1 | B2 | add tree with delete of modified blob (F-1) |
| 24 | B1 | T1 | T1 | add tree with deleted blob (F-1) |
| 25 | T1 | x | x | independently deleted tree (SAFE+MISSING) |
| 26 | T1 | x | B1/Bi | independently deleted tree AND untrack/ign blob (F-1) |
| 27- | T1 | x | T1 | deleted tree (MAYBE SAFE) |
| 28+ | T1 | B1 | x | deleted tree AND added blob (SAFE+MISSING) |
| 29 | T1 | B1 | B1 | independently typechanged tree -> blob (F-1) |
| 30+ | T1 | B1 | B2 | typechange tree->blob with conflicting blob (F-1) |
| 31* | T1 | B1 | T1/T2 | typechange tree->blob (MAYBE SAFE) |
| 32+ | T1 | T1 | x | restore locally deleted tree (SAFE+MISSING) |
| 33 | T1 | T1 | B1/Bi | locally typechange tree->untrack/ign blob (DIRTY) |
| 34 | T1 | T1 | T1/T2 | unmodified tree (MAYBE SAFE) |
| 35+ | T1 | T2 | x | update locally deleted tree (SAFE+MISSING) |
| 36* | T1 | T2 | B1/Bi | update to tree with typechanged tree->blob conflict (F-1) |
| 37 | T1 | T2 | T1/T2/T3 | update to existing tree (MAYBE SAFE) |
The number is followed by ' ' if no change is needed or '+' if the case
needs to write to disk or '-' if something must be deleted and '*' if
@ -169,30 +170,30 @@ there should be a delete followed by an write.
There are four tiers of safe cases:
- SAFE == completely safe to update
- SAFE+MISSING == safe except the workdir is missing the expect content
- MAYBE SAFE == safe if workdir tree matches (or is missing) baseline
* SAFE == completely safe to update
* SAFE+MISSING == safe except the workdir is missing the expect content
* MAYBE SAFE == safe if workdir tree matches (or is missing) baseline
content, which is unknown at this point
- FORCEABLE == conflict unless FORCE is given
- DIRTY == no conflict but change is not applied unless FORCE
* FORCEABLE == conflict unless FORCE is given
* DIRTY == no conflict but change is not applied unless FORCE
Some slightly unusual circumstances:
8 - parent dir is only deleted when file is, so parent will be left if
* 8 - parent dir is only deleted when file is, so parent will be left if
empty even though it would be deleted if the file were present
11 - core git does not consider this a conflict but attempts to delete T1
* 11 - core git does not consider this a conflict but attempts to delete T1
and gives "unable to unlink file" error yet does not skip the rest
of the operation
12 - without FORCE file is left deleted (i.e. not restored) so new wd is
* 12 - without FORCE file is left deleted (i.e. not restored) so new wd is
dirty (and warning message "D file" is printed), with FORCE, file is
restored.
24 - This should be considered MAYBE SAFE since effectively it is 7 and 8
* 24 - This should be considered MAYBE SAFE since effectively it is 7 and 8
combined, but core git considers this a conflict unless forced.
26 - This combines two cases (1 & 25) (and also implied 8 for tree content)
* 26 - This combines two cases (1 & 25) (and also implied 8 for tree content)
which are ok on their own, but core git treat this as a conflict.
If not forced, this is a conflict. If forced, this actually doesn't
have to write anything and leaves the new blob as an untracked file.
32 - This is the only case where the baseline and target values match
* 32 - This is the only case where the baseline and target values match
and yet we will still write to the working directory. In all other
cases, if baseline == target, we don't touch the workdir (it is
either already right or is "dirty"). However, since this case also

View File

@ -45,44 +45,48 @@ Internal Objects
* `git_diff_file_content` is an internal structure that represents the
data on one side of an item to be diffed; it is an augmented
`git_diff_file` with more flags and the actual file data.
** it is created from a repository plus a) a git_diff_file, b) a git_blob,
* it is created from a repository plus a) a git_diff_file, b) a git_blob,
or c) raw data and size
** there are three main operations on git_diff_file_content:
*** _initialization_ sets up the data structure and does what it can up to,
* there are three main operations on git_diff_file_content:
* _initialization_ sets up the data structure and does what it can up to,
but not including loading and looking at the actual data
*** _loading_ loads the data, preprocesses it (i.e. applies filters) and
* _loading_ loads the data, preprocesses it (i.e. applies filters) and
potentially analyzes it (to decide if binary)
*** _free_ releases loaded data and frees any allocated memory
* _free_ releases loaded data and frees any allocated memory
* The internal structure of a `git_diff_patch` stores the actual diff
between a pair of `git_diff_file_content` items
** it may be "unset" if the items are not diffable
** "empty" if the items are the same
** otherwise it will consist of a set of hunks each of which covers some
* it may be "unset" if the items are not diffable
* "empty" if the items are the same
* otherwise it will consist of a set of hunks each of which covers some
number of lines of context, additions and deletions
** a patch is created from two git_diff_file_content items
** a patch is fully instantiated in three phases:
*** initial creation and initialization
*** loading of data and preliminary data examination
*** diffing of data and optional storage of diffs
** (TBD) if a patch is asked to store the diffs and the size of the diff
* a patch is created from two git_diff_file_content items
* a patch is fully instantiated in three phases:
* initial creation and initialization
* loading of data and preliminary data examination
* diffing of data and optional storage of diffs
* (TBD) if a patch is asked to store the diffs and the size of the diff
is significantly smaller than the raw data of the two sides, then the
patch may be flattened using a pool of string data
* `git_diff_output` is an internal structure that represents an output
target for a `git_diff_patch`
** It consists of file, hunk, and line callbacks, plus a payload
** There is a standard flattened output that can be used for plain text output
** Typically we use a `git_xdiff_output` which drives the callbacks via the
* It consists of file, hunk, and line callbacks, plus a payload
* There is a standard flattened output that can be used for plain text output
* Typically we use a `git_xdiff_output` which drives the callbacks via the
xdiff code taken from core Git.
* `git_diff_driver` is an internal structure that encapsulates the logic
for a given type of file
** a driver is looked up based on the name and mode of a file.
** the driver can then be used to:
*** determine if a file is binary (by attributes, by git_diff_options
* a driver is looked up based on the name and mode of a file.
* the driver can then be used to:
* determine if a file is binary (by attributes, by git_diff_options
settings, or by examining the content)
*** give you a function pointer that is used to evaluate function context
* give you a function pointer that is used to evaluate function context
for hunk headers
** At some point, the logic for getting a filtered version of file content
* At some point, the logic for getting a filtered version of file content
or calculating the OID of a file may be moved into the driver.

View File

@ -1,111 +1,270 @@
Error reporting in libgit2
==========================
Error reporting is performed on an explicit `git_error **` argument, which appears at the end of all API calls that can return an error. Yes, this does clutter the API.
Libgit2 tries to follow the POSIX style: functions return an `int` value
with 0 (zero) indicating success and negative values indicating an error.
There are specific negative error codes for each "expected failure"
(e.g. `GIT_ENOTFOUND` for files that take a path which might be missing)
and a generic error code (-1) for all critical or non-specific failures
(e.g. running out of memory or system corruption).
When a function fails, an error is set on the error variable **and** returns one of the generic error codes.
When a negative value is returned, an error message is also set. The
message can be accessed via the `giterr_last` function which will return a
pointer to a `git_error` structure containing the error message text and
the class of error (i.e. what part of the library generated the error).
For instance: An object lookup by SHA prefix (`git_object_lookup_prefix`)
has two expected failure cases: the SHA is not found at all which returns
`GIT_ENOTFOUND` or the SHA prefix is ambiguous (i.e. two or more objects
share the prefix) which returns `GIT_EAMBIGUOUS`. There are any number of
critical failures (such as a packfile being corrupted, a loose object
having the wrong access permissions, etc.) all of which will return -1.
When the object lookup is successful, it will return 0.
If libgit2 was compiled with threads enabled (`-DTHREADSAFE=ON` when using
CMake), then the error message will be kept in thread-local storage, so it
will not be modified by other threads. If threads are not enabled, then
the error message is in global data.
All of the error return codes, the `git_error` type, the error access
functions, and the error classes are defined in `include/git2/errors.h`.
See the documentation there for details on the APIs for accessing,
clearing, and even setting error codes.
When writing libgit2 code, please be smart and conservative when returning
error codes. Functions usually have a maximum of two or three "expected
errors" and in most cases only one. If you feel there are more possible
expected error scenarios, then the API you are writing may be at too high
a level for core libgit2.
Example usage
-------------
When using libgit2, you will typically capture the return value from
functions using an `int` variable and check to see if it is negative.
When that happens, you can, if you wish, look at the specific value or
look at the error message that was generated.
~~~c
int git_repository_open(git_repository **repository, const char *path, git_error **error)
{
// perform some opening
if (p_exists(path) < 0) {
giterr_set(error, GITERR_REPOSITORY, "The path '%s' doesn't exist", path);
return GIT_ENOTFOUND;
git_repository *repo;
int error = git_repository_open(&repo, "path/to/repo");
if (error < 0) {
fprintf(stderr, "Could not open repository: %s\n", giterr_last()->message);
exit(1);
}
...
... use `repo` here ...
if (try_to_parse(path, error) < 0)
return GIT_ERROR;
git_repository_free(repo); /* void function - no error return code */
}
~~~
Some of the error return values do have meaning. Optionally, you can look
at the specific error values to decide what to do.
~~~c
{
git_repository *repo;
const char *path = "path/to/repo";
int error = git_repository_open(&repo, path);
if (error < 0) {
if (error == GIT_ENOTFOUND)
fprintf(stderr, "Could not find repository at path '%s'\n", path);
else
fprintf(stderr, "Unable to open repository: %s\n",
giterr_last()->message);
exit(1);
}
... happy ...
}
~~~
Some of the higher-level language bindings may use a range of information
from libgit2 to convert error return codes into exceptions, including the
specific error return codes and even the class of error and the error
message returned by `giterr_last`, but the full range of that logic is
beyond the scope of this document.
Example internal implementation
-------------------------------
Internally, libgit2 detects error scenarios, records error messages, and
returns error values. Errors from low-level functions are generally
passed upwards (unless the higher level can either handle the error or
wants to translate the error into something more meaningful).
~~~c
int git_repository_open(git_repository **repository, const char *path)
{
/* perform some logic to open the repository */
if (p_exists(path) < 0) {
giterr_set(GITERR_REPOSITORY, "The path '%s' doesn't exist", path);
return GIT_ENOTFOUND;
}
...
}
~~~
The simple error API
The public error API
--------------------
- `void giterr_set(git_error **, int, const char *, ...)`: the main function used to set an error. It allocates a new error object and stores it in the passed error pointer. It has no return value. The arguments for `giterr_set` are as follows:
- `const git_error *giterr_last(void)`: The main function used to look up
the last error. This may return NULL if no error has occurred.
Otherwise this should return a `git_error` object indicating the class
of error and the error message that was generated by the library.
- `git_error **error_ptr`: the pointer where the error will be created.
- `int error_class`: the class for the error. This is **not** an error code: this is an specific enum that specifies the error family. The point is to map these families 1-1 with Exception types on higher level languages (e.g. GitRepositoryException)
- `const char *error_str, ...`: the error string, with optional formatting arguments
The last error is stored in thread-local storage when libgit2 is
compiled with thread support, so you do not have to worry about another
thread overwriting the value. When thread support is off, the last
error is a global value.
- `void giterr_free(git_error *)`: takes an error and frees it. This function is available in the external API.
_Note_ There are some known bugs in the library where this may return
NULL even when an error code was generated. Please report these as
bugs, but in the meantime, please code defensively and check for NULL
when calling this function.
- `void giterr_clear(git_error **)`: clears an error previously set in an error pointer, setting it to NULL and calling `giterr_free` on it.
- `void geterr_clear(void)`: This function clears the last error. The
library will call this when an error is generated by low level function
and the higher level function handles the error.
- `void giterr_propagate(git_error **, git_error *)`: moves an error to a given error pointer, handling the case when the error pointer is NULL (in that case the error gets freed, because it cannot be propagated).
_Note_ There are some known bugs in the library where a low level
function's error message is not cleared by higher level code that
handles the error and returns zero. Please report these as bugs, but in
the meantime, a zero return value from a libgit2 API does not guarantee
that `giterr_last()` will return NULL.
The new error code return values
--------------------------------
- `void giterr_set_str(int error_class, const char *message)`: This
function can be used when writing a custom backend module to set the
libgit2 error message. See the documentation on this function for its
use. Normal usage of libgit2 will probably never need to call this API.
We are doing this the POSIX way: one error code for each "expected failure", and a generic error code for all the critical failures.
- `void giterr_set_oom(void)`: This is a standard function for reporting
an out-of-memory error. It is written in a manner that it doesn't have
to allocate any extra memory in order to record the error, so this is
the best way to report that scenario.
For instance: A reference lookup can have an expected failure (which is when the reference cannot be found), and a critical failure (which could be any of a long list of things that could go wrong, such as the refs packfile being corrupted, a loose ref being written with the wrong permissions, etc). We cannot have distinct error codes for every single error in the library, hence `git_reference_lookup` would return GIT_SUCCESS if the operation was successful, GIT_ENOTFOUND when the reference doesn't exist, and GIT_ERROR when an error happens -- **the error is then detailed in the `git_error` parameter**.
Deviations from the standard
----------------------------
Please be smart when returning error codes. Functions have max two "expected errors", and in most cases only one.
There are some public functions that do not return `int` values. There
are two primary cases:
* `void` return values: If a function has a `void` return, then it will
never fail. This primary will be used for object destructors.
* `git_xyz *` return values: These are simple accessor functions where the
only meaningful error would typically be looking something up by index
and having the index be out of bounds. In those cases, the function
will typically return NULL.
* Boolean return values: There are some cases where a function cannot fail
and wants to return a boolean value. In those cases, we try to return 1
for true and 0 for false. These cases are rare and the return value for
the function should probably be an `unsigned int` to denote these cases.
If you find an exception, please open an issue and let's fix it.
There are a few other exceptions to these rules here and there in the
library, but those are extremely rare and should probably be converted
over to other to more standard patterns for usage. Feel free to open
issues pointing these out.
There are some known bugs in the library where some functions may return a
negative value but not set an error message and some other functions may
return zero (no error) and yet leave an error message set. Please report
these cases as issues and they will be fixed. In the meanwhile, please
code defensively, checking that the return value of `giterr_last` is not
NULL before using it, and not relying on `giterr_last` to return NULL when
a function returns 0 for success.
The internal error API
----------------------
- `void giterr_set(int error_class, const char *fmt, ...)`: This is the
main internal function for setting an error. It works like `printf` to
format the error message. See the notes of `giterr_set_str` for a
general description of how error messages are stored (and also about
special handling for `error_class` of `GITERR_OS`).
Writing error messages
----------------------
Here are some guidelines when writing error messages:
- Use proper English, and an impersonal or past tenses: *The given path does not exist*, *Failed to lookup object in ODB*
- Use proper English, and an impersonal or past tenses: *The given path
does not exist*, *Failed to lookup object in ODB*
- Use short, direct and objective messages. **One line, max**. libgit2 is a low level library: think that all the messages reported will be thrown as Ruby or Python exceptions. Think how long are common exception messages in those languages.
- Use short, direct and objective messages. **One line, max**. libgit2 is
a low level library: think that all the messages reported will be thrown
as Ruby or Python exceptions. Think how long are common exception
messages in those languages.
- **Do not add redundant information to the error message**, specially information that can be inferred from the context.
- **Do not add redundant information to the error message**, specially
information that can be inferred from the context.
E.g. in `git_repository_open`, do not report a message like "Failed to open repository: path not found". Somebody is
calling that function. If it fails, he already knows that the repository failed to open!
E.g. in `git_repository_open`, do not report a message like "Failed to
open repository: path not found". Somebody is calling that
function. If it fails, they already know that the repository failed to
open!
General guidelines for error reporting
--------------------------------------
- We never handle programming errors with these functions. Programming errors are `assert`ed, and when their source is internal, fixed as soon as possible. This is C, people.
- Libgit2 does not handle programming errors with these
functions. Programming errors are `assert`ed, and when their source is
internal, fixed as soon as possible. This is C, people.
Example of programming errors that would **not** be handled: passing NULL to a function that expects a valid pointer; passing a `git_tree` to a function that expects a `git_commit`. All these cases need to be identified with `assert` and fixed asap.
Example of programming errors that would **not** be handled: passing
NULL to a function that expects a valid pointer; passing a `git_tree`
to a function that expects a `git_commit`. All these cases need to be
identified with `assert` and fixed asap.
Example of a runtime error: failing to parse a `git_tree` because it contains invalid data. Failing to open a file because it doesn't exist on disk. These errors would be handled, and a `git_error` would be set.
Example of a runtime error: failing to parse a `git_tree` because it
contains invalid data. Failing to open a file because it doesn't exist
on disk. These errors are handled, a meaningful error message is set,
and an error code is returned.
- The `git_error **` argument is always the last in the signature of all API calls. No exceptions.
- In general, *do not* try to overwrite errors internally and *do*
propagate error codes from lower level functions to the higher level.
There are some cases where propagating an error code will be more
confusing rather than less, so there are some exceptions to this rule,
but the default behavior should be to simply clean up and pass the error
on up to the caller.
- When the programmer (or us, internally) doesn't need error handling, he can pass `NULL` to the `git_error **` param. This means that the errors won't be *reported*, but obviously they still will be handled (i.e. the failing function will interrupt and return cleanly). This is transparently handled by `giterr_set`
- `git_error *` **must be initialized to `NULL` before passing its value to a function!!**
**WRONG**
~~~c
git_error *err;
git_error *good_error = NULL;
git_foo_func(arg1, arg2, &error); // invalid: `error` is not initialized
git_foo_func2(arg1, arg2, &good_error); // OK!
git_foo_func3(arg1, arg2, NULL); // OK! But no error reporting!
~~~
- Piling up errors is an error! Don't do this! Errors must always be free'd when a function returns.
~~~c
git_error *error = NULL;
git_foo_func1(arg1, &error);
git_foo_func2(arg2, &error); // WRONG! What if func1 failed? `error` would leak!
~~~
- Likewise: do not rethrow errors internally!
~~~c
int git_commit_create(..., git_error **error)
int git_commit_parent(...)
{
if (git_reference_exists("HEAD", error) < 0) {
/* HEAD does not exist; create it so we can commit... */
if (git_reference_create("HEAD", error) < 0) {
/* error could be rethrown */
}
...
if (git_commit_lookup(parent, repo, parent_id) < 0) {
giterr_set(GITERR_COMMIT, "Overwrite lookup error message");
return -1; /* mask error code */
}
- Remember that errors are now allocated, and hence they need to be free'd after they've been used. Failure to do so internally (e.g. in the already seen examples of error piling) will be reported by Valgrind, so we can easily find where are we rethrowing errors.
...
}
~~~
- Remember that any function that fails **will set an error object**, and that object will be freed.
**RIGHT**
~~~c
int git_commit_parent(...)
{
...
error = git_commit_lookup(parent, repo, parent_id);
if (error < 0) {
/* cleanup intermediate objects if necessary */
/* leave error message and propagate error code */
return error;
}
...
}
~~~

6
examples/.gitignore vendored
View File

@ -2,4 +2,10 @@ general
showindex
diff
rev-list
blame
cat-file
init
log
rev-parse
status
*.dSYM

16
examples/CMakeLists.txt Normal file
View File

@ -0,0 +1,16 @@
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})
IF(NOT ${app_name} STREQUAL "common")
ADD_EXECUTABLE(${app_name} ${src_app} "common.c")
TARGET_LINK_LIBRARIES(${app_name} git2)
ENDIF()
ENDFOREACH()

121
examples/COPYING Normal file
View File

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View File

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

View File

@ -1,11 +1,22 @@
libgit2 examples
================
These examples are meant as thin, easy-to-read snippets for Docurium
(https://github.com/github/docurium) rather than full-blown
implementations of Git commands. They are not vetted as carefully
for bugs, error handling, or cross-platform compatibility as the
rest of the code in libgit2, so copy with some caution.
These examples are a mixture of basic emulation of core Git command line
functions and simple snippets demonstrating libgit2 API usage (for use
with Docurium). As a whole, they are not vetted carefully for bugs, error
handling, and cross-platform compatibility in the same manner as the rest
of the code in libgit2, so copy with caution.
For HTML versions, check "Examples" at http://libgit2.github.com/libgit2
That being said, you are welcome to copy code from these examples as
desired when using libgit2. They have been [released to the public domain][cc0],
so there are no restrictions on their use.
[cc0]: COPYING
For annotated HTML versions, see the "Examples" section of:
http://libgit2.github.com/libgit2
such as:
http://libgit2.github.com/libgit2/ex/HEAD/general.html

159
examples/add.c Normal file
View File

@ -0,0 +1,159 @@
/*
* libgit2 "add" example - shows how to modify the index
*
* Written by the libgit2 contributors
*
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along
* with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
#include <assert.h>
enum print_options {
SKIP = 1,
VERBOSE = 2,
UPDATE = 4,
};
struct print_payload {
enum print_options options;
git_repository *repo;
};
/* Forward declarations for helpers */
static void parse_opts(int *options, int *count, int argc, char *argv[]);
void init_array(git_strarray *array, int argc, char **argv);
int print_matched_cb(const char *path, const char *matched_pathspec, void *payload);
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 options = 0, count = 0;
struct print_payload payload = {0};
git_threads_init();
parse_opts(&options, &count, argc, argv);
init_array(&array, argc-count, argv+count);
check_lg2(git_repository_open(&repo, "."), "No git repository", NULL);
check_lg2(git_repository_index(&index, repo), "Could not open repository index", NULL);
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;
}
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 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;
}
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");
exit(1);
}
static void parse_opts(int *options, int *count, int argc, char *argv[])
{
int i;
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();
}
}
if (argc<=i)
print_usage();
*count = i;
}

199
examples/blame.c Normal file
View File

@ -0,0 +1,199 @@
/*
* libgit2 "blame" example - shows how to use the blame API
*
* Written by the libgit2 contributors
*
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along
* with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/**
* This example demonstrates how to invoke the libgit2 blame API to roughly
* simulate the output of `git blame` and a few of its command line arguments.
*/
struct opts {
char *path;
char *commitspec;
int C;
int M;
int start_line;
int end_line;
};
static void parse_opts(struct opts *o, int argc, char *argv[]);
int main(int argc, char *argv[])
{
int i, line, break_on_null_hunk;
char spec[1024] = {0};
struct opts o = {0};
const char *rawdata;
git_repository *repo = NULL;
git_revspec revspec = {0};
git_blame_options blameopts = GIT_BLAME_OPTIONS_INIT;
git_blame *blame = NULL;
git_blob *blob;
git_object *obj;
git_threads_init();
parse_opts(&o, argc, argv);
if (o.M) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
if (o.C) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
/** Open the repository. */
check_lg2(git_repository_open_ext(&repo, ".", 0, NULL), "Couldn't open repository", NULL);
/**
* The commit range comes in "commitish" form. Use the rev-parse API to
* nail down the end points.
*/
if (o.commitspec) {
check_lg2(git_revparse(&revspec, repo, o.commitspec), "Couldn't parse commit spec", NULL);
if (revspec.flags & GIT_REVPARSE_SINGLE) {
git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.from));
git_object_free(revspec.from);
} else {
git_oid_cpy(&blameopts.oldest_commit, git_object_id(revspec.from));
git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.to));
git_object_free(revspec.from);
git_object_free(revspec.to);
}
}
/** Run the blame. */
check_lg2(git_blame_file(&blame, repo, o.path, &blameopts), "Blame error", NULL);
/**
* Get the raw data inside the blob for output. We use the
* `commitish:path/to/file.txt` format to find it.
*/
if (git_oid_iszero(&blameopts.newest_commit))
strcpy(spec, "HEAD");
else
git_oid_tostr(spec, sizeof(spec), &blameopts.newest_commit);
strcat(spec, ":");
strcat(spec, o.path);
check_lg2(git_revparse_single(&obj, repo, spec), "Object lookup error", NULL);
check_lg2(git_blob_lookup(&blob, repo, git_object_id(obj)), "Blob lookup error", NULL);
git_object_free(obj);
rawdata = git_blob_rawcontent(blob);
/** Produce the output. */
line = 1;
i = 0;
break_on_null_hunk = 0;
while (i < git_blob_rawsize(blob)) {
const char *eol = strchr(rawdata+i, '\n');
char oid[10] = {0};
const git_blame_hunk *hunk = git_blame_get_hunk_byline(blame, line);
if (break_on_null_hunk && !hunk) break;
if (hunk) {
break_on_null_hunk = 1;
char sig[128] = {0};
git_oid_tostr(oid, 10, &hunk->final_commit_id);
snprintf(sig, 30, "%s <%s>", hunk->final_signature->name, hunk->final_signature->email);
printf("%s ( %-30s %3d) %.*s\n",
oid,
sig,
line,
(int)(eol-rawdata-i),
rawdata+i);
}
i = (int)(eol - rawdata + 1);
line++;
}
/** Cleanup. */
git_blob_free(blob);
git_blame_free(blame);
git_repository_free(repo);
git_threads_shutdown();
return 0;
}
/** Tell the user how to make this thing work. */
static void usage(const char *msg, const char *arg)
{
if (msg && arg)
fprintf(stderr, "%s: %s\n", msg, arg);
else if (msg)
fprintf(stderr, "%s\n", msg);
fprintf(stderr, "usage: blame [options] [<commit range>] <path>\n");
fprintf(stderr, "\n");
fprintf(stderr, " <commit range> example: `HEAD~10..HEAD`, or `1234abcd`\n");
fprintf(stderr, " -L <n,m> process only line range n-m, counting from 1\n");
fprintf(stderr, " -M find line moves within and across files\n");
fprintf(stderr, " -C find line copies within and across files\n");
fprintf(stderr, "\n");
exit(1);
}
/** Parse the arguments. */
static void parse_opts(struct opts *o, int argc, char *argv[])
{
int i;
char *bare_args[3] = {0};
if (argc < 2) usage(NULL, NULL);
for (i=1; i<argc; i++) {
char *a = argv[i];
if (a[0] != '-') {
int i=0;
while (bare_args[i] && i < 3) ++i;
if (i >= 3)
usage("Invalid argument set", NULL);
bare_args[i] = a;
}
else if (!strcmp(a, "--"))
continue;
else if (!strcasecmp(a, "-M"))
o->M = 1;
else if (!strcasecmp(a, "-C"))
o->C = 1;
else if (!strcasecmp(a, "-L")) {
i++; a = argv[i];
if (i >= argc) fatal("Not enough arguments to -L", NULL);
check_lg2(sscanf(a, "%d,%d", &o->start_line, &o->end_line)-2, "-L format error", NULL);
}
else {
/* commit range */
if (o->commitspec) fatal("Only one commit spec allowed", NULL);
o->commitspec = a;
}
}
/* Handle the bare arguments */
if (!bare_args[0]) usage("Please specify a path", NULL);
o->path = bare_args[0];
if (bare_args[1]) {
/* <commitspec> <path> */
o->path = bare_args[1];
o->commitspec = bare_args[0];
}
if (bare_args[2]) {
/* <oldcommit> <newcommit> <path> */
char spec[128] = {0};
o->path = bare_args[2];
sprintf(spec, "%s..%s", bare_args[0], bare_args[1]);
o->commitspec = spec;
}
}

View File

@ -1,37 +1,18 @@
#include <stdio.h>
#include <git2.h>
#include <stdlib.h>
#include <string.h>
/*
* libgit2 "cat-file" example - shows how to print data from the ODB
*
* Written by the libgit2 contributors
*
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along
* with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
static git_repository *g_repo;
static void check(int error, const char *message)
{
if (error) {
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: cat-file (-t | -s | -e | -p) [<options>] <object>\n");
exit(1);
}
static int check_str_param(
const char *arg, const char *pattern, const char **val)
{
size_t len = strlen(pattern);
if (strncmp(arg, pattern, len))
return 0;
*val = (const char *)(arg + len);
return 1;
}
#include "common.h"
static void print_signature(const char *header, const git_signature *sig)
{
@ -57,12 +38,14 @@ static void print_signature(const char *header, const git_signature *sig)
sign, hours, minutes);
}
/** Printing out a blob is simple, get the contents and print */
static void show_blob(const git_blob *blob)
{
/* ? Does this need crlf filtering? */
fwrite(git_blob_rawcontent(blob), git_blob_rawsize(blob), 1, stdout);
}
/** Show each entry with its type, id and attributes */
static void show_tree(const git_tree *tree)
{
size_t i, max_i = (int)git_tree_entrycount(tree);
@ -81,6 +64,9 @@ static void show_tree(const git_tree *tree)
}
}
/**
* Commits and tags have a few interesting fields in their header.
*/
static void show_commit(const git_commit *commit)
{
unsigned int i, max_i;
@ -123,53 +109,34 @@ enum {
SHOW_PRETTY = 4
};
/* Forward declarations for option-parsing helper */
struct opts {
const char *dir;
const char *rev;
int action;
int verbose;
};
static void parse_opts(struct opts *o, int argc, char *argv[]);
/** Entry point for this command */
int main(int argc, char *argv[])
{
const char *dir = ".", *rev = NULL;
int i, action = 0, verbose = 0;
git_repository *repo;
struct opts o = { ".", NULL, 0, 0 };
git_object *obj = NULL;
char oidstr[GIT_OID_HEXSZ + 1];
git_threads_init();
for (i = 1; i < argc; ++i) {
char *a = argv[i];
parse_opts(&o, argc, argv);
if (a[0] != '-') {
if (rev != NULL)
usage("Only one rev should be provided", NULL);
else
rev = a;
}
else if (!strcmp(a, "-t"))
action = SHOW_TYPE;
else if (!strcmp(a, "-s"))
action = SHOW_SIZE;
else if (!strcmp(a, "-e"))
action = SHOW_NONE;
else if (!strcmp(a, "-p"))
action = SHOW_PRETTY;
else if (!strcmp(a, "-q"))
verbose = 0;
else if (!strcmp(a, "-v"))
verbose = 1;
else if (!strcmp(a, "--help") || !strcmp(a, "-h"))
usage(NULL, NULL);
else if (!check_str_param(a, "--git-dir=", &dir))
usage("Unknown option", a);
}
check_lg2(git_repository_open_ext(&repo, o.dir, 0, NULL),
"Could not open repository", NULL);
check_lg2(git_revparse_single(&obj, repo, o.rev),
"Could not resolve", o.rev);
if (!action || !rev)
usage(NULL, NULL);
check(git_repository_open_ext(&g_repo, dir, 0, NULL),
"Could not open repository");
if (git_revparse_single(&obj, g_repo, rev) < 0) {
fprintf(stderr, "Could not resolve '%s'\n", rev);
exit(1);
}
if (verbose) {
if (o.verbose) {
char oidstr[GIT_OID_HEXSZ + 1];
git_oid_tostr(oidstr, sizeof(oidstr), git_object_id(obj));
@ -177,7 +144,7 @@ int main(int argc, char *argv[])
git_object_type2string(git_object_type(obj)), oidstr);
}
switch (action) {
switch (o.action) {
case SHOW_TYPE:
printf("%s\n", git_object_type2string(git_object_type(obj)));
break;
@ -185,9 +152,9 @@ int main(int argc, char *argv[])
git_odb *odb;
git_odb_object *odbobj;
check(git_repository_odb(&odb, g_repo), "Could not open ODB");
check(git_odb_read(&odbobj, odb, git_object_id(obj)),
"Could not find obj");
check_lg2(git_repository_odb(&odb, repo), "Could not open ODB", NULL);
check_lg2(git_odb_read(&odbobj, odb, git_object_id(obj)),
"Could not find obj", NULL);
printf("%ld\n", (long)git_odb_object_size(odbobj));
@ -221,9 +188,59 @@ int main(int argc, char *argv[])
}
git_object_free(obj);
git_repository_free(g_repo);
git_repository_free(repo);
git_threads_shutdown();
return 0;
}
/** Print out usage information */
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: cat-file (-t | -s | -e | -p) [-v] [-q] "
"[-h|--help] [--git-dir=<dir>] <object>\n");
exit(1);
}
/** Parse the command-line options taken from git */
static void parse_opts(struct opts *o, int argc, char *argv[])
{
struct args_info args = ARGS_INFO_INIT;
for (args.pos = 1; args.pos < argc; ++args.pos) {
char *a = argv[args.pos];
if (a[0] != '-') {
if (o->rev != NULL)
usage("Only one rev should be provided", NULL);
else
o->rev = a;
}
else if (!strcmp(a, "-t"))
o->action = SHOW_TYPE;
else if (!strcmp(a, "-s"))
o->action = SHOW_SIZE;
else if (!strcmp(a, "-e"))
o->action = SHOW_NONE;
else if (!strcmp(a, "-p"))
o->action = SHOW_PRETTY;
else if (!strcmp(a, "-q"))
o->verbose = 0;
else if (!strcmp(a, "-v"))
o->verbose = 1;
else if (!strcmp(a, "--help") || !strcmp(a, "-h"))
usage(NULL, NULL);
else if (!match_str_arg(&o->dir, &args, "--git-dir"))
usage("Unknown option", a);
}
if (!o->action || !o->rev)
usage(NULL, NULL);
}

191
examples/common.c Normal file
View File

@ -0,0 +1,191 @@
/*
* Utilities library for libgit2 examples
*
* Written by the libgit2 contributors
*
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along
* with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
void check_lg2(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);
}
void fatal(const char *message, const char *extra)
{
if (extra)
fprintf(stderr, "%s %s\n", message, extra);
else
fprintf(stderr, "%s\n", message);
exit(1);
}
size_t is_prefixed(const char *str, const char *pfx)
{
size_t len = strlen(pfx);
return strncmp(str, pfx, len) ? 0 : len;
}
int match_str_arg(
const char **out, struct args_info *args, const char *opt)
{
const char *found = args->argv[args->pos];
size_t len = is_prefixed(found, opt);
if (!len)
return 0;
if (!found[len]) {
if (args->pos + 1 == args->argc)
fatal("expected value following argument", opt);
args->pos += 1;
*out = args->argv[args->pos];
return 1;
}
if (found[len] == '=') {
*out = found + len + 1;
return 1;
}
return 0;
}
static const char *match_numeric_arg(struct args_info *args, const char *opt)
{
const char *found = args->argv[args->pos];
size_t len = is_prefixed(found, opt);
if (!len)
return NULL;
if (!found[len]) {
if (args->pos + 1 == args->argc)
fatal("expected numeric value following argument", opt);
args->pos += 1;
found = args->argv[args->pos];
} else {
found = found + len;
if (*found == '=')
found++;
}
return found;
}
int match_uint16_arg(
uint16_t *out, struct args_info *args, const char *opt)
{
const char *found = match_numeric_arg(args, opt);
uint16_t val;
char *endptr = NULL;
if (!found)
return 0;
val = (uint16_t)strtoul(found, &endptr, 0);
if (!endptr || *endptr != '\0')
fatal("expected number after argument", opt);
if (out)
*out = val;
return 1;
}
static int match_int_internal(
int *out, const char *str, int allow_negative, const char *opt)
{
char *endptr = NULL;
int val = (int)strtol(str, &endptr, 10);
if (!endptr || *endptr != '\0')
fatal("expected number", opt);
else if (val < 0 && !allow_negative)
fatal("negative values are not allowed", opt);
if (out)
*out = val;
return 1;
}
int is_integer(int *out, const char *str, int allow_negative)
{
return match_int_internal(out, str, allow_negative, NULL);
}
int match_int_arg(
int *out, struct args_info *args, const char *opt, int allow_negative)
{
const char *found = match_numeric_arg(args, opt);
if (!found)
return 0;
return match_int_internal(out, found, allow_negative, opt);
}
int diff_output(
const git_diff_delta *d,
const git_diff_hunk *h,
const git_diff_line *l,
void *p)
{
FILE *fp = p;
(void)d; (void)h;
if (!fp)
fp = stdout;
if (l->origin == GIT_DIFF_LINE_CONTEXT ||
l->origin == GIT_DIFF_LINE_ADDITION ||
l->origin == GIT_DIFF_LINE_DELETION)
fputc(l->origin, fp);
fwrite(l->content, 1, l->content_len, fp);
return 0;
}
void treeish_to_tree(
git_tree **out, git_repository *repo, const char *treeish)
{
git_object *obj = NULL;
check_lg2(
git_revparse_single(&obj, repo, treeish),
"looking up object", treeish);
check_lg2(
git_object_peel((git_object **)out, obj, GIT_OBJ_TREE),
"resolving object to tree", treeish);
git_object_free(obj);
}

87
examples/common.h Normal file
View File

@ -0,0 +1,87 @@
/*
* Utilities library for libgit2 examples
*
* Written by the libgit2 contributors
*
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along
* with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <git2.h>
/**
* Check libgit2 error code, printing error to stderr on failure and
* exiting the program.
*/
extern void check_lg2(int error, const char *message, const char *extra);
/**
* Exit the program, printing error to stderr
*/
extern void fatal(const char *message, const char *extra);
/**
* Check if a string has the given prefix. Returns 0 if not prefixed
* or the length of the prefix if it is.
*/
extern size_t is_prefixed(const char *str, const char *pfx);
/**
* Match an integer string, returning 1 if matched, 0 if not.
*/
extern int is_integer(int *out, const char *str, int allow_negative);
struct args_info {
int argc;
char **argv;
int pos;
};
#define ARGS_INFO_INIT { argc, argv, 0 }
/**
* Check current `args` entry against `opt` string. If it matches
* exactly, take the next arg as a string; if it matches as a prefix with
* an equal sign, take the remainder as a string; otherwise return 0.
*/
extern int match_str_arg(
const char **out, struct args_info *args, const char *opt);
/**
* Check current `args` entry against `opt` string parsing as uint16. If
* `opt` matches exactly, take the next arg as a uint16_t value; if `opt`
* is a prefix (equal sign optional), take the remainder of the arg as a
* uint16_t value; otherwise return 0.
*/
extern int match_uint16_arg(
uint16_t *out, struct args_info *args, const char *opt);
/**
* Check current `args` entry against `opt` string parsing as int. If
* `opt` matches exactly, take the next arg as an int value; if it matches
* as a prefix (equal sign optional), take the remainder of the arg as a
* int value; otherwise return 0.
*/
extern int match_int_arg(
int *out, struct args_info *args, const char *opt, int allow_negative);
/**
* Basic output function for plain text diff output
* Pass `FILE*` such as `stdout` or `stderr` as payload (or NULL == `stdout`)
*/
extern int diff_output(
const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*);
/**
* Convert a treeish argument to an actual tree; this will call check_lg2
* and exit the program if `treeish` cannot be resolved to a tree
*/
extern void treeish_to_tree(
git_tree **out, git_repository *repo, const char *treeish);

View File

@ -1,41 +1,31 @@
#include <stdio.h>
#include <git2.h>
#include <stdlib.h>
#include <string.h>
/*
* libgit2 "diff" example - shows how to use the diff API
*
* Written by the libgit2 contributors
*
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along
* with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
static void check(int error, const char *message)
{
if (error) {
fprintf(stderr, "%s (%d)\n", message, error);
exit(1);
}
}
#include "common.h"
static int resolve_to_tree(
git_repository *repo, const char *identifier, git_tree **tree)
{
int err = 0;
git_object *obj = NULL;
/**
* This example demonstrates the use of the libgit2 diff APIs to
* create `git_diff` objects and display them, emulating a number of
* core Git `diff` command line options.
*
* This covers on a portion of the core Git diff options and doesn't
* have particularly good error handling, but it should show most of
* the core libgit2 diff APIs, including various types of diffs and
* how to do renaming detection and patch formatting.
*/
if ((err = git_revparse_single(&obj, repo, identifier)) < 0)
return err;
switch (git_object_type(obj)) {
case GIT_OBJ_TREE:
*tree = (git_tree *)obj;
break;
case GIT_OBJ_COMMIT:
err = git_commit_tree(tree, (git_commit *)obj);
git_object_free(obj);
break;
default:
err = GIT_ENOTFOUND;
}
return err;
}
char *colors[] = {
static const char *colors[] = {
"\033[m", /* reset */
"\033[1m", /* bold */
"\033[31m", /* red */
@ -43,65 +33,110 @@ char *colors[] = {
"\033[36m" /* cyan */
};
static int printer(
const git_diff_delta *delta,
const git_diff_range *range,
char usage,
const char *line,
size_t line_len,
void *data)
/** The 'opts' struct captures all the various parsed command line options. */
struct opts {
git_diff_options diffopts;
git_diff_find_options findopts;
int color;
int cached;
git_diff_format_t format;
const char *treeish1;
const char *treeish2;
const char *dir;
};
/** These functions are implemented at the end */
static void parse_opts(struct opts *o, int argc, char *argv[]);
static int color_printer(
const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*);
int main(int argc, char *argv[])
{
int *last_color = data, color = 0;
git_repository *repo = NULL;
git_tree *t1 = NULL, *t2 = NULL;
git_diff *diff;
struct opts o = {
GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT,
-1, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "."
};
(void)delta; (void)range; (void)line_len;
git_threads_init();
if (*last_color >= 0) {
switch (usage) {
case GIT_DIFF_LINE_ADDITION: color = 3; break;
case GIT_DIFF_LINE_DELETION: color = 2; break;
case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break;
case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break;
case GIT_DIFF_LINE_FILE_HDR: color = 1; break;
case GIT_DIFF_LINE_HUNK_HDR: color = 4; break;
default: color = 0;
parse_opts(&o, argc, argv);
check_lg2(git_repository_open_ext(&repo, o.dir, 0, NULL),
"Could not open repository", o.dir);
/**
* Possible argument patterns:
*
* * &lt;sha1&gt; &lt;sha2&gt;
* * &lt;sha1&gt; --cached
* * &lt;sha1&gt;
* * --cached
* * nothing
*
* Currently ranged arguments like &lt;sha1&gt;..&lt;sha2&gt; and &lt;sha1&gt;...&lt;sha2&gt;
* are not supported in this example
*/
if (o.treeish1)
treeish_to_tree(&t1, repo, o.treeish1);
if (o.treeish2)
treeish_to_tree(&t2, repo, o.treeish2);
if (t1 && t2)
check_lg2(
git_diff_tree_to_tree(&diff, repo, t1, t2, &o.diffopts),
"diff trees", NULL);
else if (t1 && o.cached)
check_lg2(
git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts),
"diff tree to index", NULL);
else if (t1)
check_lg2(
git_diff_tree_to_workdir_with_index(&diff, repo, t1, &o.diffopts),
"diff tree to working directory", NULL);
else if (o.cached) {
treeish_to_tree(&t1, repo, "HEAD");
check_lg2(
git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts),
"diff tree to index", NULL);
}
if (color != *last_color) {
if (*last_color == 1 || color == 1)
else
check_lg2(
git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts),
"diff index to working directory", NULL);
/** Apply rename and copy detection if requested. */
if ((o.findopts.flags & GIT_DIFF_FIND_ALL) != 0)
check_lg2(
git_diff_find_similar(diff, &o.findopts),
"finding renames and copies", NULL);
/** Generate simple output using libgit2 display helper. */
if (o.color >= 0)
fputs(colors[0], stdout);
fputs(colors[color], stdout);
*last_color = color;
}
}
fputs(line, stdout);
return 0;
}
check_lg2(
git_diff_print(diff, o.format, color_printer, &o.color),
"displaying diff", NULL);
static int check_uint16_param(const char *arg, const char *pattern, uint16_t *val)
{
size_t len = strlen(pattern);
uint16_t strval;
char *endptr = NULL;
if (strncmp(arg, pattern, len))
return 0;
if (arg[len] == '\0' && pattern[len - 1] != '=')
return 1;
if (arg[len] == '=')
len++;
strval = strtoul(arg + len, &endptr, 0);
if (endptr == arg)
return 0;
*val = strval;
return 1;
}
if (o.color >= 0)
fputs(colors[0], stdout);
/** Cleanup before exiting. */
git_diff_free(diff);
git_tree_free(t1);
git_tree_free(t2);
git_repository_free(repo);
git_threads_shutdown();
static int check_str_param(const char *arg, const char *pattern, const char **val)
{
size_t len = strlen(pattern);
if (strncmp(arg, pattern, len))
return 0;
*val = (const char *)(arg + len);
return 1;
}
static void usage(const char *message, const char *arg)
@ -114,152 +149,109 @@ static void usage(const char *message, const char *arg)
exit(1);
}
enum {
FORMAT_PATCH = 0,
FORMAT_COMPACT = 1,
FORMAT_RAW = 2
};
int main(int argc, char *argv[])
/** This implements very rudimentary colorized output. */
static int color_printer(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
const git_diff_line *line,
void *data)
{
git_repository *repo = NULL;
git_tree *t1 = NULL, *t2 = NULL;
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
git_diff_list *diff;
int i, color = -1, format = FORMAT_PATCH, cached = 0;
char *a, *treeish1 = NULL, *treeish2 = NULL;
const char *dir = ".";
int *last_color = data, color = 0;
git_threads_init();
(void)delta; (void)hunk;
/* parse arguments as copied from git-diff */
if (*last_color >= 0) {
switch (line->origin) {
case GIT_DIFF_LINE_ADDITION: color = 3; break;
case GIT_DIFF_LINE_DELETION: color = 2; break;
case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break;
case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break;
case GIT_DIFF_LINE_FILE_HDR: color = 1; break;
case GIT_DIFF_LINE_HUNK_HDR: color = 4; break;
default: break;
}
for (i = 1; i < argc; ++i) {
a = argv[i];
if (color != *last_color) {
if (*last_color == 1 || color == 1)
fputs(colors[0], stdout);
fputs(colors[color], stdout);
*last_color = color;
}
}
return diff_output(delta, hunk, line, stdout);
}
/** Parse arguments as copied from git-diff. */
static void parse_opts(struct opts *o, int argc, char *argv[])
{
struct args_info args = ARGS_INFO_INIT;
for (args.pos = 1; args.pos < argc; ++args.pos) {
const char *a = argv[args.pos];
if (a[0] != '-') {
if (treeish1 == NULL)
treeish1 = a;
else if (treeish2 == NULL)
treeish2 = a;
if (o->treeish1 == NULL)
o->treeish1 = a;
else if (o->treeish2 == NULL)
o->treeish2 = a;
else
usage("Only one or two tree identifiers can be provided", NULL);
}
else if (!strcmp(a, "-p") || !strcmp(a, "-u") ||
!strcmp(a, "--patch"))
format = FORMAT_PATCH;
o->format = GIT_DIFF_FORMAT_PATCH;
else if (!strcmp(a, "--cached"))
cached = 1;
o->cached = 1;
else if (!strcmp(a, "--name-only"))
o->format = GIT_DIFF_FORMAT_NAME_ONLY;
else if (!strcmp(a, "--name-status"))
format = FORMAT_COMPACT;
o->format = GIT_DIFF_FORMAT_NAME_STATUS;
else if (!strcmp(a, "--raw"))
format = FORMAT_RAW;
o->format = GIT_DIFF_FORMAT_RAW;
else if (!strcmp(a, "--color"))
color = 0;
o->color = 0;
else if (!strcmp(a, "--no-color"))
color = -1;
o->color = -1;
else if (!strcmp(a, "-R"))
opts.flags |= GIT_DIFF_REVERSE;
o->diffopts.flags |= GIT_DIFF_REVERSE;
else if (!strcmp(a, "-a") || !strcmp(a, "--text"))
opts.flags |= GIT_DIFF_FORCE_TEXT;
o->diffopts.flags |= GIT_DIFF_FORCE_TEXT;
else if (!strcmp(a, "--ignore-space-at-eol"))
opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL;
o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL;
else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change"))
opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE;
o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE;
else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space"))
opts.flags |= GIT_DIFF_IGNORE_WHITESPACE;
o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE;
else if (!strcmp(a, "--ignored"))
opts.flags |= GIT_DIFF_INCLUDE_IGNORED;
o->diffopts.flags |= GIT_DIFF_INCLUDE_IGNORED;
else if (!strcmp(a, "--untracked"))
opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
else if (check_uint16_param(a, "-M", &findopts.rename_threshold) ||
check_uint16_param(a, "--find-renames",
&findopts.rename_threshold))
findopts.flags |= GIT_DIFF_FIND_RENAMES;
else if (check_uint16_param(a, "-C", &findopts.copy_threshold) ||
check_uint16_param(a, "--find-copies",
&findopts.copy_threshold))
findopts.flags |= GIT_DIFF_FIND_COPIES;
o->diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
else if (match_uint16_arg(
&o->findopts.rename_threshold, &args, "-M") ||
match_uint16_arg(
&o->findopts.rename_threshold, &args, "--find-renames"))
o->findopts.flags |= GIT_DIFF_FIND_RENAMES;
else if (match_uint16_arg(
&o->findopts.copy_threshold, &args, "-C") ||
match_uint16_arg(
&o->findopts.copy_threshold, &args, "--find-copies"))
o->findopts.flags |= GIT_DIFF_FIND_COPIES;
else if (!strcmp(a, "--find-copies-harder"))
findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
else if (!strncmp(a, "-B", 2) || !strncmp(a, "--break-rewrites", 16)) {
o->findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites"))
/* TODO: parse thresholds */
findopts.flags |= GIT_DIFF_FIND_REWRITES;
o->findopts.flags |= GIT_DIFF_FIND_REWRITES;
else if (!match_uint16_arg(
&o->diffopts.context_lines, &args, "-U") &&
!match_uint16_arg(
&o->diffopts.context_lines, &args, "--unified") &&
!match_uint16_arg(
&o->diffopts.interhunk_lines, &args, "--inter-hunk-context") &&
!match_str_arg(&o->diffopts.old_prefix, &args, "--src-prefix") &&
!match_str_arg(&o->diffopts.new_prefix, &args, "--dst-prefix") &&
!match_str_arg(&o->dir, &args, "--git-dir"))
usage("Unknown command line argument", a);
}
else if (!check_uint16_param(a, "-U", &opts.context_lines) &&
!check_uint16_param(a, "--unified=", &opts.context_lines) &&
!check_uint16_param(a, "--inter-hunk-context=",
&opts.interhunk_lines) &&
!check_str_param(a, "--src-prefix=", &opts.old_prefix) &&
!check_str_param(a, "--dst-prefix=", &opts.new_prefix) &&
!check_str_param(a, "--git-dir=", &dir))
usage("Unknown arg", a);
}
/* open repo */
check(git_repository_open_ext(&repo, dir, 0, NULL),
"Could not open repository");
if (treeish1)
check(resolve_to_tree(repo, treeish1, &t1), "Looking up first tree");
if (treeish2)
check(resolve_to_tree(repo, treeish2, &t2), "Looking up second tree");
/* <sha1> <sha2> */
/* <sha1> --cached */
/* <sha1> */
/* --cached */
/* nothing */
if (t1 && t2)
check(git_diff_tree_to_tree(&diff, repo, t1, t2, &opts), "Diff");
else if (t1 && cached)
check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff");
else if (t1) {
git_diff_list *diff2;
check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff");
check(git_diff_index_to_workdir(&diff2, repo, NULL, &opts), "Diff");
check(git_diff_merge(diff, diff2), "Merge diffs");
git_diff_list_free(diff2);
}
else if (cached) {
check(resolve_to_tree(repo, "HEAD", &t1), "looking up HEAD");
check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff");
}
else
check(git_diff_index_to_workdir(&diff, repo, NULL, &opts), "Diff");
if ((findopts.flags & GIT_DIFF_FIND_ALL) != 0)
check(git_diff_find_similar(diff, &findopts),
"finding renames and copies ");
if (color >= 0)
fputs(colors[0], stdout);
switch (format) {
case FORMAT_PATCH:
check(git_diff_print_patch(diff, printer, &color), "Displaying diff");
break;
case FORMAT_COMPACT:
check(git_diff_print_compact(diff, printer, &color), "Displaying diff");
break;
case FORMAT_RAW:
check(git_diff_print_raw(diff, printer, &color), "Displaying diff");
break;
}
if (color >= 0)
fputs(colors[0], stdout);
git_diff_list_free(diff);
git_tree_free(t1);
git_tree_free(t2);
git_repository_free(repo);
git_threads_shutdown();
return 0;
}

View File

@ -1,3 +1,17 @@
/*
* libgit2 "general" example - shows basic libgit2 concepts
*
* Written by the libgit2 contributors
*
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along
* with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
// [**libgit2**][lg] is a portable, pure C implementation of the Git core
// methods provided as a re-entrant linkable library with a solid API,
// allowing you to write native speed custom Git applications in any
@ -52,7 +66,7 @@ int main (int argc, char** argv)
// simplest. There are also [methods][me] for specifying the index file
// and work tree locations, here we assume they are in the normal places.
//
// (Try running this program against tests-clar/resources/testrepo.git.)
// (Try running this program against tests/resources/testrepo.git.)
//
// [me]: http://libgit2.github.com/libgit2/#HEAD/group/repository
int error;

253
examples/init.c Normal file
View File

@ -0,0 +1,253 @@
/*
* libgit2 "init" example - shows how to initialize a new repo
*
* Written by the libgit2 contributors
*
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along
* with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/**
* 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.
*/
/** Forward declarations of helpers */
struct opts {
int no_options;
int quiet;
int bare;
int initial_commit;
uint32_t shared;
const char *template;
const char *gitdir;
const char *dir;
};
static void create_initial_commit(git_repository *repo);
static void parse_opts(struct opts *o, int argc, char *argv[]);
int main(int argc, char *argv[])
{
git_repository *repo = NULL;
struct opts o = { 1, 0, 0, 0, GIT_REPOSITORY_INIT_SHARED_UMASK, 0, 0, 0 };
git_threads_init();
parse_opts(&o, argc, argv);
/* Initialize repository. */
if (o.no_options) {
/**
* No options were specified, so let's demonstrate the default
* simple case of git_repository_init() API usage...
*/
check_lg2(git_repository_init(&repo, o.dir, 0),
"Could not initialize repository", NULL);
}
else {
/**
* Some command line options were specified, so we'll use the
* extended init API to handle them
*/
git_repository_init_options initopts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
initopts.flags = GIT_REPOSITORY_INIT_MKPATH;
if (o.bare)
initopts.flags |= GIT_REPOSITORY_INIT_BARE;
if (o.template) {
initopts.flags |= GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
initopts.template_path = o.template;
}
if (o.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)
*/
initopts.workdir_path = o.dir;
o.dir = o.gitdir;
}
if (o.shared != 0)
initopts.mode = o.shared;
check_lg2(git_repository_init_ext(&repo, o.dir, &initopts),
"Could not initialize repository", NULL);
}
/** Print a message to stdout like "git init" does. */
if (!o.quiet) {
if (o.bare || o.gitdir)
o.dir = git_repository_path(repo);
else
o.dir = git_repository_workdir(repo);
printf("Initialized empty Git repository in %s\n", o.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 (o.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)
fatal("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)
fatal("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)
fatal("Unable to write initial tree from index", NULL);
git_index_free(index);
if (git_tree_lookup(&tree, repo, &tree_id) < 0)
fatal("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)
fatal("Could not create the initial commit", NULL);
/** Clean up so we don't leak memory. */
git_tree_free(tree);
git_signature_free(sig);
}
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>]\n"
" [--shared[=perms]] [--initial-commit]\n"
" [--separate-git-dir] <directory>\n");
exit(1);
}
/** 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;
}
static void parse_opts(struct opts *o, int argc, char *argv[])
{
struct args_info args = ARGS_INFO_INIT;
const char *sharedarg;
/** Process arguments. */
for (args.pos = 1; args.pos < argc; ++args.pos) {
char *a = argv[args.pos];
if (a[0] == '-')
o->no_options = 0;
if (a[0] != '-') {
if (o->dir != NULL)
usage("extra argument", a);
o->dir = a;
}
else if (!strcmp(a, "-q") || !strcmp(a, "--quiet"))
o->quiet = 1;
else if (!strcmp(a, "--bare"))
o->bare = 1;
else if (!strcmp(a, "--shared"))
o->shared = GIT_REPOSITORY_INIT_SHARED_GROUP;
else if (!strcmp(a, "--initial-commit"))
o->initial_commit = 1;
else if (match_str_arg(&sharedarg, &args, "--shared"))
o->shared = parse_shared(sharedarg);
else if (!match_str_arg(&o->template, &args, "--template") ||
!match_str_arg(&o->gitdir, &args, "--separate-git-dir"))
usage("unknown option", a);
}
if (!o->dir)
usage("must specify directory to init", NULL);
}

434
examples/log.c Normal file
View File

@ -0,0 +1,434 @@
/*
* libgit2 "log" example - shows how to walk history and get commit info
*
* Written by the libgit2 contributors
*
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along
* with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/**
* This example demonstrates the libgit2 rev walker APIs to roughly
* simulate the output of `git log` and a few of command line arguments.
* `git log` has many many options and this only shows a few of them.
*
* This does not have:
*
* - Robust error handling
* - Colorized or paginated output formatting
* - Most of the `git log` options
*
* This does have:
*
* - Examples of translating command line arguments to equivalent libgit2
* revwalker configuration calls
* - Simplified options to apply pathspec limits and to show basic diffs
*/
/** log_state represents walker being configured while handling options */
struct log_state {
git_repository *repo;
const char *repodir;
git_revwalk *walker;
int hide;
int sorting;
int revisions;
};
/** utility functions that are called to configure the walker */
static void set_sorting(struct log_state *s, unsigned int sort_mode);
static void push_rev(struct log_state *s, git_object *obj, int hide);
static int add_revision(struct log_state *s, const char *revstr);
/** log_options holds other command line options that affect log output */
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;
};
/** utility functions that parse options and help with log output */
static int parse_options(
struct log_state *s, struct log_options *opt, int argc, char **argv);
static void print_time(const git_time *intime, const char *prefix);
static void print_commit(git_commit *commit);
static int match_with_parent(git_commit *commit, int i, git_diff_options *);
int main(int argc, char *argv[])
{
int i, count = 0, printed = 0, parents, last_arg;
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();
/** Parse arguments and set up revwalker. */
last_arg = parse_options(&s, &opt, argc, argv);
diffopts.pathspec.strings = &argv[last_arg];
diffopts.pathspec.count = argc - last_arg;
if (diffopts.pathspec.count > 0)
check_lg2(git_pathspec_new(&ps, &diffopts.pathspec),
"Building pathspec", NULL);
if (!s.revisions)
add_revision(&s, NULL);
/** Use the revwalker to traverse the history. */
printed = count = 0;
for (; !git_revwalk_next(&oid, s.walker); git_commit_free(commit)) {
check_lg2(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_lg2(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 *diff = NULL;
if (parents > 1)
continue;
check_lg2(git_commit_tree(&b, commit), "Get tree", NULL);
if (parents == 1) {
git_commit *parent;
check_lg2(git_commit_parent(&parent, commit, 0), "Get parent", NULL);
check_lg2(git_commit_tree(&a, parent), "Tree for parent", NULL);
git_commit_free(parent);
}
check_lg2(git_diff_tree_to_tree(
&diff, git_commit_owner(commit), a, b, &diffopts),
"Diff commit with parent", NULL);
check_lg2(
git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, diff_output, NULL),
"Displaying diff", NULL);
git_diff_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;
}
/** Push object (for hide or show) onto revwalker. */
static void push_rev(struct log_state *s, git_object *obj, int hide)
{
hide = s->hide ^ hide;
/** Create revwalker on demand if it doesn't already exist. */
if (!s->walker) {
check_lg2(git_revwalk_new(&s->walker, s->repo),
"Could not create revision walker", NULL);
git_revwalk_sorting(s->walker, s->sorting);
}
if (!obj)
check_lg2(git_revwalk_push_head(s->walker),
"Could not find repository HEAD", NULL);
else if (hide)
check_lg2(git_revwalk_hide(s->walker, git_object_id(obj)),
"Reference does not refer to a commit", NULL);
else
check_lg2(git_revwalk_push(s->walker, git_object_id(obj)),
"Reference does not refer to a commit", NULL);
git_object_free(obj);
}
/** Parse revision string and add revs to walker. */
static int add_revision(struct log_state *s, const char *revstr)
{
git_revspec revs;
int hide = 0;
/** Open repo on demand if it isn't already open. */
if (!s->repo) {
if (!s->repodir) s->repodir = ".";
check_lg2(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_lg2(git_merge_base(&base, s->repo,
git_object_id(revs.from), git_object_id(revs.to)),
"Could not find merge base", revstr);
check_lg2(
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;
}
/** Update revwalker with sorting mode. */
static void set_sorting(struct log_state *s, unsigned int sort_mode)
{
/** Open repo on demand if it isn't already open. */
if (!s->repo) {
if (!s->repodir) s->repodir = ".";
check_lg2(git_repository_open_ext(&s->repo, s->repodir, 0, NULL),
"Could not open repository", s->repodir);
}
/** Create revwalker on demand if it doesn't already exist. */
if (!s->walker)
check_lg2(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);
}
/** Helper to format a git_time value like Git. */
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);
intm = gmtime(&t);
strftime(out, sizeof(out), "%a %b %e %T %Y", intm);
printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes);
}
/** Helper to print a commit object. */
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");
}
/** Helper to find how many files in a commit changed from its nth parent. */
static int match_with_parent(git_commit *commit, int i, git_diff_options *opts)
{
git_commit *parent;
git_tree *a, *b;
git_diff *diff;
int ndeltas;
check_lg2(
git_commit_parent(&parent, commit, (size_t)i), "Get parent", NULL);
check_lg2(git_commit_tree(&a, parent), "Tree for parent", NULL);
check_lg2(git_commit_tree(&b, commit), "Tree for commit", NULL);
check_lg2(
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_free(diff);
git_tree_free(a);
git_tree_free(b);
git_commit_free(parent);
return ndeltas > 0;
}
/** Print a usage message for the program. */
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);
}
/** Parse some log command line options. */
static int parse_options(
struct log_state *s, struct log_options *opt, int argc, char **argv)
{
struct args_info args = ARGS_INFO_INIT;
memset(s, 0, sizeof(*s));
s->sorting = GIT_SORT_TIME;
memset(opt, 0, sizeof(*opt));
opt->max_parents = -1;
opt->limit = -1;
for (args.pos = 1; args.pos < argc; ++args.pos) {
const char *a = argv[args.pos];
if (a[0] != '-') {
if (!add_revision(s, a))
s->revisions++;
else
/** Try failed revision parse as filename. */
break;
} else if (!strcmp(a, "--")) {
++args.pos;
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 (match_str_arg(&s->repodir, &args, "--git-dir"))
/** Found git-dir. */;
else if (match_int_arg(&opt->skip, &args, "--skip", 0))
/** Found valid --skip. */;
else if (match_int_arg(&opt->limit, &args, "--max-count", 0))
/** Found valid --max-count. */;
else if (a[1] >= '0' && a[1] <= '9')
is_integer(&opt->limit, a + 1, 0);
else if (match_int_arg(&opt->limit, &args, "-n", 0))
/** Found valid -n. */;
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, &args, "--max-parents=", 1))
/** Found valid --max-parents. */;
else if (match_int_arg(&opt->min_parents, &args, "--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);
}
return args.pos;
}

View File

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

View File

@ -9,19 +9,6 @@
# include <unistd.h>
#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 {
git_transfer_progress fetch_progress;
size_t completed_steps;
@ -38,6 +25,11 @@ static void print_progress(const progress_data *pd)
: 0.f;
int kbytes = pd->fetch_progress.received_bytes / 1024;
if (pd->fetch_progress.received_objects == pd->fetch_progress.total_objects) {
printf("Resolving deltas %d/%d\r",
pd->fetch_progress.indexed_deltas,
pd->fetch_progress.total_deltas);
} else {
printf("net %3d%% (%4d kb, %5d/%5d) / idx %3d%% (%5d/%5d) / chk %3d%% (%4" PRIuZ "/%4" PRIuZ ") %s\n",
network_percent, kbytes,
pd->fetch_progress.received_objects, pd->fetch_progress.total_objects,
@ -45,6 +37,7 @@ static void print_progress(const progress_data *pd)
checkout_percent,
pd->completed_steps, pd->total_steps,
pd->path);
}
}
static int fetch_progress(const git_transfer_progress *stats, void *payload)
@ -63,24 +56,6 @@ static void checkout_progress(const char *path, size_t cur, size_t tot, void *pa
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)
{
@ -105,9 +80,9 @@ int do_clone(git_repository *repo, int argc, char **argv)
checkout_opts.progress_cb = checkout_progress;
checkout_opts.progress_payload = &pd;
clone_opts.checkout_opts = checkout_opts;
clone_opts.fetch_progress_cb = &fetch_progress;
clone_opts.fetch_progress_payload = &pd;
clone_opts.cred_acquire_cb = cred_acquire;
clone_opts.remote_callbacks.transfer_progress = &fetch_progress;
clone_opts.remote_callbacks.credentials = cred_acquire_cb;
clone_opts.remote_callbacks.payload = &pd;
// Do the clone
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 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
/* Define the printf format specifer to use for size_t output */
#if defined(_MSC_VER) || defined(__MINGW32__)

View File

@ -14,11 +14,12 @@ struct dl_data {
int finished;
};
static void progress_cb(const char *str, int len, void *data)
static int progress_cb(const char *str, int len, void *data)
{
(void)data;
printf("remote: %.*s", len, str);
fflush(stdout); /* We don't have the \n to force the flush */
return 0;
}
static void *download(void *ptr)
@ -35,7 +36,7 @@ static void *download(void *ptr)
// Download the packfile and index it. This function updates the
// amount of received data and the indexer stats which lets you
// inform the user about progress.
if (git_remote_download(data->remote, NULL, NULL) < 0) {
if (git_remote_download(data->remote) < 0) {
data->ret = -1;
goto exit;
}
@ -47,6 +48,11 @@ exit:
return &data->ret;
}
/**
* This function gets called for each remote-tracking branch that gets
* updated. The message we output depends on whether it's a new one or
* an update.
*/
static int update_cb(const char *refname, const git_oid *a, const git_oid *b, void *data)
{
char a_str[GIT_OID_HEXSZ+1], b_str[GIT_OID_HEXSZ+1];
@ -66,6 +72,7 @@ static int update_cb(const char *refname, const git_oid *a, const git_oid *b, vo
return 0;
}
/** Entry point for this command */
int fetch(git_repository *repo, int argc, char **argv)
{
git_remote *remote = NULL;
@ -91,6 +98,7 @@ int fetch(git_repository *repo, int argc, char **argv)
// Set up the callbacks (only update_tips for now)
callbacks.update_tips = &update_cb;
callbacks.progress = &progress_cb;
callbacks.credentials = cred_acquire_cb;
git_remote_set_callbacks(remote, &callbacks);
// Set up the information for the background worker thread
@ -112,10 +120,14 @@ int fetch(git_repository *repo, int argc, char **argv)
do {
usleep(10000);
if (stats->total_objects > 0)
if (stats->received_objects == stats->total_objects) {
printf("Resolving deltas %d/%d\r",
stats->indexed_deltas, stats->total_deltas);
} else if (stats->total_objects > 0) {
printf("Received %d/%d objects (%d) in %" PRIuZ " bytes\r",
stats->received_objects, stats->total_objects,
stats->indexed_objects, stats->received_bytes);
}
} while (!data.finished);
if (data.ret < 0)
@ -124,8 +136,18 @@ int fetch(git_repository *repo, int argc, char **argv)
pthread_join(worker, NULL);
#endif
/**
* If there are local objects (we got a thin pack), then tell
* the user how many objects we saved from having to cross the
* network.
*/
if (stats->local_objects > 0) {
printf("\rReceived %d/%d objects in %zu bytes (used %d local objects)\n",
stats->indexed_objects, stats->total_objects, stats->received_bytes, stats->local_objects);
} else{
printf("\rReceived %d/%d objects in %zu bytes\n",
stats->indexed_objects, stats->total_objects, stats->received_bytes);
}
// Disconnect the underlying connection to prevent from idling.
git_remote_disconnect(remote);

View File

@ -31,7 +31,7 @@ static int index_cb(const git_transfer_progress *stats, void *data)
int index_pack(git_repository *repo, int argc, char **argv)
{
git_indexer_stream *idx;
git_indexer *idx;
git_transfer_progress stats = {0, 0};
int error;
char hash[GIT_OID_HEXSZ + 1] = {0};
@ -46,7 +46,7 @@ int index_pack(git_repository *repo, int argc, char **argv)
return EXIT_FAILURE;
}
if (git_indexer_stream_new(&idx, ".", NULL, NULL) < 0) {
if (git_indexer_new(&idx, ".", 0, NULL, NULL, NULL) < 0) {
puts("bad idx");
return -1;
}
@ -61,7 +61,7 @@ int index_pack(git_repository *repo, int argc, char **argv)
if (read_bytes < 0)
break;
if ((error = git_indexer_stream_add(idx, buf, read_bytes, &stats)) < 0)
if ((error = git_indexer_append(idx, buf, read_bytes, &stats)) < 0)
goto cleanup;
index_cb(&stats, NULL);
@ -73,16 +73,16 @@ int index_pack(git_repository *repo, int argc, char **argv)
goto cleanup;
}
if ((error = git_indexer_stream_finalize(idx, &stats)) < 0)
if ((error = git_indexer_commit(idx, &stats)) < 0)
goto cleanup;
printf("\rIndexing %d of %d\n", stats.indexed_objects, stats.total_objects);
git_oid_fmt(hash, git_indexer_stream_hash(idx));
git_oid_fmt(hash, git_indexer_hash(idx));
puts(hash);
cleanup:
close(fd);
git_indexer_stream_free(idx);
git_indexer_free(idx);
return error;
}

View File

@ -4,65 +4,52 @@
#include <string.h>
#include "common.h"
static int show_ref__cb(git_remote_head *head, void *payload)
{
char oid[GIT_OID_HEXSZ + 1] = {0};
(void)payload;
git_oid_fmt(oid, &head->oid);
printf("%s\t%s\n", oid, head->name);
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)
{
git_remote *remote = NULL;
int error;
const git_remote_head **refs;
size_t refs_len, i;
git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
// Find the remote by name
error = git_remote_load(&remote, repo, name);
if (error < 0) {
error = git_remote_create_inmemory(&remote, repo, NULL, name);
if (error < 0)
goto cleanup;
}
/**
* Connect to the remote and call the printing function for
* each of the remote references.
*/
callbacks.credentials = cred_acquire_cb;
git_remote_set_callbacks(remote, &callbacks);
error = git_remote_connect(remote, GIT_DIRECTION_FETCH);
if (error < 0)
goto cleanup;
error = git_remote_ls(remote, &show_ref__cb, NULL);
/**
* Get the list of references on the remote and print out
* their name next to what they point to.
*/
if (git_remote_ls(&refs, &refs_len, remote) < 0)
goto cleanup;
for (i = 0; i < refs_len; i++) {
char oid[GIT_OID_HEXSZ + 1] = {0};
git_oid_fmt(oid, &refs[i]->oid);
printf("%s\t%s\n", oid, refs[i]->name);
}
cleanup:
git_remote_free(remote);
return error;
}
// This gets called to do the work. The remote can be given either as
// the name of a configured remote or an URL.
/** Entry point for this command */
int ls_remote(git_repository *repo, int argc, char **argv)
{
int error;
@ -72,12 +59,7 @@ int ls_remote(git_repository *repo, int argc, char **argv)
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]);
}
return error;
}

View File

@ -1,17 +1,43 @@
#include <stdio.h>
#include <string.h>
/*
* libgit2 "rev-list" example - shows how to transform a rev-spec into a list
* of commit ids
*
* Written by the libgit2 contributors
*
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along
* with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include <git2.h>
#include "common.h"
static void check_error(int error_code, const char *action)
static int revwalk_parseopts(git_repository *repo, git_revwalk *walk, int nopts, char **opts);
int main (int argc, char **argv)
{
if (!error_code)
return;
git_repository *repo;
git_revwalk *walk;
git_oid oid;
char buf[41];
const git_error *error = giterr_last();
fprintf(stderr, "Error %d %s: %s\n", -error_code, action,
(error && error->message) ? error->message : "???");
exit(1);
git_threads_init();
check_lg2(git_repository_open_ext(&repo, ".", 0, NULL), "opening repository", NULL);
check_lg2(git_revwalk_new(&walk, repo), "allocating revwalk", NULL);
check_lg2(revwalk_parseopts(repo, walk, argc-1, argv+1), "parsing options", NULL);
while (!git_revwalk_next(&oid, walk)) {
git_oid_fmt(buf, &oid);
buf[40] = '\0';
printf("%s\n", buf);
}
git_threads_shutdown();
return 0;
}
static int push_commit(git_revwalk *walk, const git_oid *oid, int hide)
@ -93,27 +119,3 @@ static int revwalk_parseopts(git_repository *repo, git_revwalk *walk, int nopts,
return 0;
}
int main (int argc, char **argv)
{
int error;
git_repository *repo;
git_revwalk *walk;
git_oid oid;
char buf[41];
error = git_repository_open_ext(&repo, ".", 0, NULL);
check_error(error, "opening repository");
error = git_revwalk_new(&walk, repo);
check_error(error, "allocating revwalk");
error = revwalk_parseopts(repo, walk, argc-1, argv+1);
check_error(error, "parsing options");
while (!git_revwalk_next(&oid, walk)) {
git_oid_fmt(buf, &oid);
buf[40] = '\0';
printf("%s\n", buf);
}
return 0;
}

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

@ -0,0 +1,115 @@
/*
* libgit2 "rev-parse" example - shows how to parse revspecs
*
* Written by the libgit2 contributors
*
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along
* with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/** Forward declarations for helpers. */
struct parse_state {
git_repository *repo;
const char *repodir;
const char *spec;
int not;
};
static void parse_opts(struct parse_state *ps, int argc, char *argv[]);
static int parse_revision(struct parse_state *ps);
int main(int argc, char *argv[])
{
struct parse_state ps = {0};
git_threads_init();
parse_opts(&ps, argc, argv);
check_lg2(parse_revision(&ps), "Parsing", NULL);
git_repository_free(ps.repo);
git_threads_shutdown();
return 0;
}
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);
}
static void parse_opts(struct parse_state *ps, int argc, char *argv[])
{
struct args_info args = ARGS_INFO_INIT;
for (args.pos=1; args.pos < argc; ++args.pos) {
const char *a = argv[args.pos];
if (a[0] != '-') {
if (ps->spec)
usage("Too many specs", a);
ps->spec = a;
} else if (!strcmp(a, "--not"))
ps->not = !ps->not;
else if (!match_str_arg(&ps->repodir, &args, "--git-dir"))
usage("Cannot handle argument", a);
}
}
static int parse_revision(struct parse_state *ps)
{
git_revspec rs;
char str[GIT_OID_HEXSZ + 1];
if (!ps->repo) {
if (!ps->repodir)
ps->repodir = ".";
check_lg2(git_repository_open_ext(&ps->repo, ps->repodir, 0, NULL),
"Could not open repository from", ps->repodir);
}
check_lg2(git_revparse(&rs, ps->repo, ps->spec), "Could not parse", ps->spec);
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_lg2(git_merge_base(&base, ps->repo,
git_object_id(rs.from), git_object_id(rs.to)),
"Could not find merge base", ps->spec);
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 {
fatal("Invalid results from git_revparse", ps->spec);
}
return 0;
}

View File

@ -1,10 +1,21 @@
#include <git2.h>
#include <stdio.h>
#include <string.h>
/*
* libgit2 "showindex" example - shows how to extract data from the index
*
* Written by the libgit2 contributors
*
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along
* with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
int main (int argc, char** argv)
{
git_repository *repo = NULL;
git_index *index;
unsigned int i, ecount;
char *dir = ".";
@ -12,31 +23,24 @@ int main (int argc, char** argv)
char out[41];
out[40] = '\0';
git_threads_init();
if (argc > 2)
fatal("usage: showindex [<repo-dir>]", NULL);
if (argc > 1)
dir = argv[1];
if (!dir || argc > 2) {
fprintf(stderr, "usage: showindex [<repo-dir>]\n");
return 1;
}
dirlen = strlen(dir);
if (dirlen > 5 && strcmp(dir + dirlen - 5, "index") == 0) {
if (git_index_open(&index, dir) < 0) {
fprintf(stderr, "could not open index: %s\n", dir);
return 1;
}
check_lg2(git_index_open(&index, dir), "could not open index", dir);
} else {
if (git_repository_open_ext(&repo, dir, 0, NULL) < 0) {
fprintf(stderr, "could not open repository: %s\n", dir);
return 1;
}
if (git_repository_index(&index, repo) < 0) {
fprintf(stderr, "could not open repository index\n");
return 1;
}
git_repository *repo;
check_lg2(git_repository_open_ext(&repo, dir, 0, NULL), "could not open repository", dir);
check_lg2(git_repository_index(&index, repo), "could not open repository index", NULL);
git_repository_free(repo);
}
git_index_read(index);
git_index_read(index, 0);
ecount = git_index_entrycount(index);
if (!ecount)
@ -60,8 +64,7 @@ int main (int argc, char** argv)
}
git_index_free(index);
git_repository_free(repo);
git_threads_shutdown();
return 0;
}

View File

@ -1,33 +1,32 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
* libgit2 "status" example - shows how to use the status APIs
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
* Written by the libgit2 contributors
*
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along
* with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#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
#include "common.h"
/*
/**
* 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
@ -35,34 +34,91 @@ enum {
* - A sample status formatter that matches the "short" format
*/
static void check(int error, const char *message, const char *extra)
enum {
FORMAT_DEFAULT = 0,
FORMAT_LONG = 1,
FORMAT_SHORT = 2,
FORMAT_PORCELAIN = 3,
};
#define MAX_PATHSPEC 8
struct opts {
git_status_options statusopt;
char *repodir;
char *pathspec[MAX_PATHSPEC];
int npaths;
int format;
int zterm;
int showbranch;
};
static void parse_opts(struct opts *o, int argc, char *argv[]);
static void show_branch(git_repository *repo, int format);
static void print_long(git_status_list *status);
static void print_short(git_repository *repo, git_status_list *status);
int main(int argc, char *argv[])
{
const git_error *lg2err;
const char *lg2msg = "", *lg2spacer = "";
git_repository *repo = NULL;
git_status_list *status;
struct opts o = { GIT_STATUS_OPTIONS_INIT, "." };
if (!error)
return;
git_threads_init();
if ((lg2err = giterr_last()) != NULL && lg2err->message != NULL) {
lg2msg = lg2err->message;
lg2spacer = " - ";
}
o.statusopt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
o.statusopt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
if (extra)
fprintf(stderr, "%s '%s' [%d]%s%s\n",
message, extra, error, lg2spacer, lg2msg);
parse_opts(&o, argc, argv);
/**
* Try to open the repository at the given path (or at the current
* directory if none was given).
*/
check_lg2(git_repository_open_ext(&repo, o.repodir, 0, NULL),
"Could not open repository", o.repodir);
if (git_repository_is_bare(repo))
fatal("Cannot report status on bare repository",
git_repository_path(repo));
/**
* Run status on the repository
*
* We use `git_status_list_new()` to generate a list of status
* information which lets us iterate over it at our
* convenience and extract the data we want to show out of
* each entry.
*
* You can use `git_status_foreach()` or
* `git_status_foreach_ext()` if you'd prefer to execute a
* callback for each entry. The latter gives you more control
* about what results are presented.
*/
check_lg2(git_status_list_new(&status, repo, &o.statusopt),
"Could not get status", NULL);
if (o.showbranch)
show_branch(repo, o.format);
if (o.format == FORMAT_LONG)
print_long(status);
else
fprintf(stderr, "%s [%d]%s%s\n",
message, error, lg2spacer, lg2msg);
print_short(repo, status);
exit(1);
}
static void fail(const char *message)
{
check(-1, message, NULL);
git_status_list_free(status);
git_repository_free(repo);
git_threads_shutdown();
return 0;
}
/**
* If the user asked for the branch, let's show the short name of the
* branch.
*/
static void show_branch(git_repository *repo, int format)
{
int error = 0;
@ -71,14 +127,12 @@ static void show_branch(git_repository *repo, int format)
error = git_repository_head(&head, repo);
if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND)
if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND)
branch = NULL;
else if (!error) {
branch = git_reference_name(head);
if (!strncmp(branch, "refs/heads/", strlen("refs/heads/")))
branch += strlen("refs/heads/");
branch = git_reference_shorthand(head);
} else
check(error, "failed to get current branch", NULL);
check_lg2(error, "failed to get current branch", NULL);
if (format == FORMAT_LONG)
printf("# On branch %s\n",
@ -89,7 +143,11 @@ static void show_branch(git_repository *repo, int format)
git_reference_free(head);
}
static void print_long(git_repository *repo, git_status_list *status)
/**
* This function print out an output similar to git's status command
* in long form, including the command-line hints.
*/
static void print_long(git_status_list *status)
{
size_t i, maxi = git_status_list_entrycount(status);
const git_status_entry *s;
@ -97,9 +155,7 @@ static void print_long(git_repository *repo, git_status_list *status)
int changed_in_workdir = 0, rm_in_workdir = 0;
const char *old_path, *new_path;
(void)repo;
/* print index changes */
/** Print index changes. */
for (i = 0; i < maxi; ++i) {
char *istatus = NULL;
@ -148,16 +204,22 @@ static void print_long(git_repository *repo, git_status_list *status)
}
header = 0;
/* print workdir changes to tracked files */
/** Print workdir changes to tracked files. */
for (i = 0; i < maxi; ++i) {
char *wstatus = NULL;
s = git_status_byindex(status, i);
/**
* With `GIT_STATUS_OPT_INCLUDE_UNMODIFIED` (not used in this example)
* `index_to_workdir` may not be `NULL` even if there are
* no differences, in which case it will be a `GIT_DELTA_UNMODIFIED`.
*/
if (s->status == GIT_STATUS_CURRENT || s->index_to_workdir == NULL)
continue;
/** Print out the output since we know the file has some changes */
if (s->status & GIT_STATUS_WT_MODIFIED)
wstatus = "modified: ";
if (s->status & GIT_STATUS_WT_DELETED)
@ -191,9 +253,8 @@ static void print_long(git_repository *repo, git_status_list *status)
changed_in_workdir = 1;
printf("#\n");
}
header = 0;
/* print untracked files */
/** Print untracked files. */
header = 0;
@ -215,7 +276,7 @@ static void print_long(git_repository *repo, git_status_list *status)
header = 0;
/* print ignored files */
/** Print ignored files. */
for (i = 0; i < maxi; ++i) {
s = git_status_byindex(status, i);
@ -237,6 +298,10 @@ static void print_long(git_repository *repo, git_status_list *status)
printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
}
/**
* This version of the output prefixes each path with two status
* columns and shows submodule status information.
*/
static void print_short(git_repository *repo, git_status_list *status)
{
size_t i, maxi = git_status_list_entrycount(status);
@ -287,6 +352,10 @@ static void print_short(git_repository *repo, git_status_list *status)
if (istatus == '?' && wstatus == '?')
continue;
/**
* A commit in a tree is how submodules are stored, so
* let's go take a look at its status.
*/
if (s->index_to_workdir &&
s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT)
{
@ -308,6 +377,10 @@ static void print_short(git_repository *repo, git_status_list *status)
}
}
/**
* Now that we have all the information, it's time to format the output.
*/
if (s->head_to_index) {
a = s->head_to_index->old_file.path;
b = s->head_to_index->new_file.path;
@ -341,103 +414,61 @@ static void print_short(git_repository *repo, git_status_list *status)
}
}
int main(int argc, char *argv[])
/**
* Parse options that git's status command supports.
*/
static void parse_opts(struct opts *o, 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];
struct args_info args = ARGS_INFO_INIT;
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 (args.pos = 1; args.pos < argc; ++args.pos) {
char *a = argv[args.pos];
for (i = 1; i < argc; ++i) {
if (argv[i][0] != '-') {
if (npaths < MAX_PATHSPEC)
pathspec[npaths++] = argv[i];
if (a[0] != '-') {
if (o->npaths < MAX_PATHSPEC)
o->pathspec[o->npaths++] = a;
else
fail("Example only supports a limited pathspec");
fatal("Example only supports a limited pathspec", NULL);
}
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(a, "-s") || !strcmp(a, "--short"))
o->format = FORMAT_SHORT;
else if (!strcmp(a, "--long"))
o->format = FORMAT_LONG;
else if (!strcmp(a, "--porcelain"))
o->format = FORMAT_PORCELAIN;
else if (!strcmp(a, "-b") || !strcmp(a, "--branch"))
o->showbranch = 1;
else if (!strcmp(a, "-z")) {
o->zterm = 1;
if (o->format == FORMAT_DEFAULT)
o->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 |
else if (!strcmp(a, "--ignored"))
o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
else if (!strcmp(a, "-uno") ||
!strcmp(a, "--untracked-files=no"))
o->statusopt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED;
else if (!strcmp(a, "-unormal") ||
!strcmp(a, "--untracked-files=normal"))
o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
else if (!strcmp(a, "-uall") ||
!strcmp(a, "--untracked-files=all"))
o->statusopt.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 if (!strcmp(a, "--ignore-submodules=all"))
o->statusopt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
else if (!strncmp(a, "--git-dir=", strlen("--git-dir=")))
o->repodir = a + strlen("--git-dir=");
else
check(-1, "Unsupported option", argv[i]);
check_lg2(-1, "Unsupported option", a);
}
if (format == FORMAT_DEFAULT)
format = FORMAT_LONG;
if (format == FORMAT_LONG)
showbranch = 1;
if (npaths > 0) {
opt.pathspec.strings = pathspec;
opt.pathspec.count = npaths;
if (o->format == FORMAT_DEFAULT)
o->format = FORMAT_LONG;
if (o->format == FORMAT_LONG)
o->showbranch = 1;
if (o->npaths > 0) {
o->statusopt.pathspec.strings = o->pathspec;
o->statusopt.pathspec.count = o->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

@ -4,7 +4,7 @@ THIS_FILE="$(readlink -f "$0")"
ROOT="$(dirname "$(dirname "$(dirname "$THIS_FILE")")")"
PROGRAM="$ROOT"/examples/rev-list
LIBDIR="$ROOT"/build
REPO="$ROOT"/tests-clar/resources/testrepo.git
REPO="$ROOT"/tests/resources/testrepo.git
cd "$REPO"

View File

@ -68,3 +68,4 @@ ok Sebastian Schuberth <sschuberth@gmail.com>
ok Shawn O. Pearce <spearce@spearce.org>
ok Steffen Prohaska <prohaska@zib.de>
ok Sven Verdoolaege <skimo@kotnet.org>
ok Torsten Bögershausen <tboegi@web.de>

View File

@ -8,53 +8,52 @@
#ifndef INCLUDE_git_git_h__
#define INCLUDE_git_git_h__
#include "git2/attr.h"
#include "git2/blob.h"
#include "git2/blame.h"
#include "git2/branch.h"
#include "git2/buffer.h"
#include "git2/checkout.h"
#include "git2/clone.h"
#include "git2/commit.h"
#include "git2/common.h"
#include "git2/config.h"
#include "git2/diff.h"
#include "git2/errors.h"
#include "git2/filter.h"
#include "git2/graph.h"
#include "git2/ignore.h"
#include "git2/index.h"
#include "git2/indexer.h"
#include "git2/merge.h"
#include "git2/message.h"
#include "git2/net.h"
#include "git2/notes.h"
#include "git2/object.h"
#include "git2/odb.h"
#include "git2/oid.h"
#include "git2/pack.h"
#include "git2/patch.h"
#include "git2/pathspec.h"
#include "git2/push.h"
#include "git2/refdb.h"
#include "git2/reflog.h"
#include "git2/refs.h"
#include "git2/refspec.h"
#include "git2/remote.h"
#include "git2/repository.h"
#include "git2/reset.h"
#include "git2/revparse.h"
#include "git2/revwalk.h"
#include "git2/signature.h"
#include "git2/stash.h"
#include "git2/status.h"
#include "git2/submodule.h"
#include "git2/tag.h"
#include "git2/threads.h"
#include "git2/transport.h"
#include "git2/tree.h"
#include "git2/types.h"
#include "git2/version.h"
#include "git2/common.h"
#include "git2/threads.h"
#include "git2/errors.h"
#include "git2/types.h"
#include "git2/oid.h"
#include "git2/signature.h"
#include "git2/odb.h"
#include "git2/repository.h"
#include "git2/revwalk.h"
#include "git2/merge.h"
#include "git2/graph.h"
#include "git2/refs.h"
#include "git2/reflog.h"
#include "git2/revparse.h"
#include "git2/object.h"
#include "git2/blob.h"
#include "git2/commit.h"
#include "git2/tag.h"
#include "git2/tree.h"
#include "git2/diff.h"
#include "git2/index.h"
#include "git2/config.h"
#include "git2/transport.h"
#include "git2/remote.h"
#include "git2/clone.h"
#include "git2/checkout.h"
#include "git2/push.h"
#include "git2/attr.h"
#include "git2/ignore.h"
#include "git2/branch.h"
#include "git2/refspec.h"
#include "git2/net.h"
#include "git2/status.h"
#include "git2/indexer.h"
#include "git2/submodule.h"
#include "git2/notes.h"
#include "git2/reset.h"
#include "git2/message.h"
#include "git2/pack.h"
#include "git2/stash.h"
#endif

198
include/git2/blame.h Normal file
View File

@ -0,0 +1,198 @@
/*
* 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_blame_h__
#define INCLUDE_git_blame_h__
#include "common.h"
#include "oid.h"
/**
* @file git2/blame.h
* @brief Git blame routines
* @defgroup git_blame Git blame routines
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
/**
* Flags for indicating option behavior for git_blame APIs.
*/
typedef enum {
/** Normal blame, the default */
GIT_BLAME_NORMAL = 0,
/** Track lines that have moved within a file (like `git blame -M`).
* NOT IMPLEMENTED. */
GIT_BLAME_TRACK_COPIES_SAME_FILE = (1<<0),
/** Track lines that have moved across files in the same commit (like `git blame -C`).
* NOT IMPLEMENTED. */
GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES = (1<<1),
/** Track lines that have been copied from another file that exists in the
* same commit (like `git blame -CC`). Implies SAME_FILE.
* NOT IMPLEMENTED. */
GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES = (1<<2),
/** Track lines that have been copied from another file that exists in *any*
* commit (like `git blame -CCC`). Implies SAME_COMMIT_COPIES.
* NOT IMPLEMENTED. */
GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES = (1<<3),
} git_blame_flag_t;
/**
* Blame options structure
*
* Use zeros to indicate default settings. It's easiest to use the
* `GIT_BLAME_OPTIONS_INIT` macro:
* git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
*
* - `flags` is a combination of the `git_blame_flag_t` values above.
* - `min_match_characters` is the lower bound on the number of alphanumeric
* characters that must be detected as moving/copying within a file for it to
* associate those lines with the parent commit. The default value is 20.
* This value only takes effect if any of the `GIT_BLAME_TRACK_COPIES_*`
* flags are specified.
* - `newest_commit` is the id of the newest commit to consider. The default
* is HEAD.
* - `oldest_commit` is the id of the oldest commit to consider. The default
* is the first commit encountered with a NULL parent.
* - `min_line` is the first line in the file to blame. The default is 1 (line
* numbers start with 1).
* - `max_line` is the last line in the file to blame. The default is the last
* line of the file.
*/
typedef struct git_blame_options {
unsigned int version;
uint32_t flags;
uint16_t min_match_characters;
git_oid newest_commit;
git_oid oldest_commit;
uint32_t min_line;
uint32_t max_line;
} git_blame_options;
#define GIT_BLAME_OPTIONS_VERSION 1
#define GIT_BLAME_OPTIONS_INIT {GIT_BLAME_OPTIONS_VERSION}
/**
* Structure that represents a blame hunk.
*
* - `lines_in_hunk` is the number of lines in this hunk
* - `final_commit_id` is the OID of the commit where this line was last
* changed.
* - `final_start_line_number` is the 1-based line number where this hunk
* begins, in the final version of the file
* - `orig_commit_id` is the OID of the commit where this hunk was found. This
* will usually be the same as `final_commit_id`, except when
* `GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES` has been specified.
* - `orig_path` is the path to the file where this hunk originated, as of the
* commit specified by `orig_commit_id`.
* - `orig_start_line_number` is the 1-based line number where this hunk begins
* in the file named by `orig_path` in the commit specified by
* `orig_commit_id`.
* - `boundary` is 1 iff the hunk has been tracked to a boundary commit (the
* root, or the commit specified in git_blame_options.oldest_commit)
*/
typedef struct git_blame_hunk {
uint16_t lines_in_hunk;
git_oid final_commit_id;
uint16_t final_start_line_number;
git_signature *final_signature;
git_oid orig_commit_id;
const char *orig_path;
uint16_t orig_start_line_number;
git_signature *orig_signature;
char boundary;
} git_blame_hunk;
/* Opaque structure to hold blame results */
typedef struct git_blame git_blame;
/**
* Gets the number of hunks that exist in the blame structure.
*/
GIT_EXTERN(uint32_t) git_blame_get_hunk_count(git_blame *blame);
/**
* Gets the blame hunk at the given index.
*
* @param blame the blame structure to query
* @param index index of the hunk to retrieve
* @return the hunk at the given index, or NULL on error
*/
GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byindex(
git_blame *blame,
uint32_t index);
/**
* Gets the hunk that relates to the given line number in the newest commit.
*
* @param blame the blame structure to query
* @param lineno the (1-based) line number to find a hunk for
* @return the hunk that contains the given line, or NULL on error
*/
GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byline(
git_blame *blame,
uint32_t lineno);
/**
* Get the blame for a single file.
*
* @param out pointer that will receive the blame object
* @param repo repository whose history is to be walked
* @param path path to file to consider
* @param options options for the blame operation. If NULL, this is treated as
* though GIT_BLAME_OPTIONS_INIT were passed.
* @return 0 on success, or an error code. (use giterr_last for information
* about the error.)
*/
GIT_EXTERN(int) git_blame_file(
git_blame **out,
git_repository *repo,
const char *path,
git_blame_options *options);
/**
* Get blame data for a file that has been modified in memory. The `reference`
* parameter is a pre-calculated blame for the in-odb history of the file. This
* means that once a file blame is completed (which can be expensive), updating
* the buffer blame is very fast.
*
* Lines that differ between the buffer and the committed version are marked as
* having a zero OID for their final_commit_id.
*
* @param out pointer that will receive the resulting blame data
* @param reference cached blame from the history of the file (usually the output
* from git_blame_file)
* @param buffer the (possibly) modified contents of the file
* @param buffer_len number of valid bytes in the buffer
* @return 0 on success, or an error code. (use giterr_last for information
* about the error)
*/
GIT_EXTERN(int) git_blame_buffer(
git_blame **out,
git_blame *reference,
const char *buffer,
uint32_t buffer_len);
/**
* Free memory allocated by git_blame_file or git_blame_buffer.
*
* @param blame the blame structure to free
*/
GIT_EXTERN(void) git_blame_free(git_blame *blame);
/** @} */
GIT_END_DECL
#endif

View File

@ -11,6 +11,7 @@
#include "types.h"
#include "oid.h"
#include "object.h"
#include "buffer.h"
/**
* @file git2/blob.h
@ -95,6 +96,37 @@ GIT_EXTERN(const void *) git_blob_rawcontent(const git_blob *blob);
*/
GIT_EXTERN(git_off_t) git_blob_rawsize(const git_blob *blob);
/**
* Get a buffer with the filtered content of a blob.
*
* This applies filters as if the blob was being checked out to the
* working directory under the specified filename. This may apply
* CRLF filtering or other types of changes depending on the file
* attributes set for the blob and the content detected in it.
*
* The output is written into a `git_buf` which the caller must free
* when done (via `git_buf_free`).
*
* If no filters need to be applied, then the `out` buffer will just be
* populated with a pointer to the raw content of the blob. In that case,
* be careful to *not* free the blob until done with the buffer. To keep
* the data detached from the blob, call `git_buf_grow` on the buffer
* with a `want_size` of 0 and the buffer will be reallocated to be
* detached from the blob.
*
* @param out The git_buf to be filled in
* @param blob Pointer to the blob
* @param as_path Path used for file attribute lookups, etc.
* @param check_for_binary_data Should this test if blob content contains
* NUL bytes / looks like binary data before applying filters?
* @return 0 on success or an error code
*/
GIT_EXTERN(int) git_blob_filtered_content(
git_buf *out,
git_blob *blob,
const char *as_path,
int check_for_binary_data);
/**
* Read a file from the working folder of a repository
* and write it to the Object Database as a loose blob

View File

@ -66,33 +66,41 @@ GIT_EXTERN(int) git_branch_create(
*/
GIT_EXTERN(int) git_branch_delete(git_reference *branch);
typedef int (*git_branch_foreach_cb)(
const char *branch_name,
git_branch_t branch_type,
void *payload);
/** Iterator type for branches */
typedef struct git_branch_iterator git_branch_iterator;
/**
* Loop over all the branches and issue a callback for each one.
*
* If the callback returns a non-zero value, this will stop looping.
* Create an iterator which loops over the requested branches.
*
* @param out the iterator
* @param repo Repository where to find the branches.
*
* @param list_flags Filtering flags for the branch
* listing. Valid values are GIT_BRANCH_LOCAL, GIT_BRANCH_REMOTE
* or a combination of the two.
*
* @param branch_cb Callback to invoke per found branch.
*
* @param payload Extra parameter to callback function.
*
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
* @return 0 on success or an error code
*/
GIT_EXTERN(int) git_branch_foreach(
GIT_EXTERN(int) git_branch_iterator_new(
git_branch_iterator **out,
git_repository *repo,
unsigned int list_flags,
git_branch_foreach_cb branch_cb,
void *payload);
git_branch_t list_flags);
/**
* Retrieve the next branch from the iterator
*
* @param out the reference
* @param out_type the type of branch (local or remote-tracking)
* @param iter the branch iterator
* @return 0 on success, GIT_ITEROVER if there are no more branches or an error code.
*/
GIT_EXTERN(int) git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *iter);
/**
* Free a branch iterator
*
* @param iter the iterator to free
*/
GIT_EXTERN(void) git_branch_iterator_free(git_branch_iterator *iter);
/**
* Move/rename an existing local branch reference.

112
include/git2/buffer.h Normal file
View File

@ -0,0 +1,112 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_git_buf_h__
#define INCLUDE_git_buf_h__
#include "common.h"
/**
* @file git2/buffer.h
* @brief Buffer export structure
*
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
/**
* A data buffer for exporting data from libgit2
*
* Sometimes libgit2 wants to return an allocated data buffer to the
* caller and have the caller take responsibility for freeing that memory.
* This can be awkward if the caller does not have easy access to the same
* allocation functions that libgit2 is using. In those cases, libgit2
* will fill in a `git_buf` and the caller can use `git_buf_free()` to
* release it when they are done.
*
* A `git_buf` may also be used for the caller to pass in a reference to
* a block of memory they hold. In this case, libgit2 will not resize or
* free the memory, but will read from it as needed.
*
* A `git_buf` is a public structure with three fields:
*
* - `ptr` points to the start of the allocated memory. If it is NULL,
* then the `git_buf` is considered empty and libgit2 will feel free
* to overwrite it with new data.
*
* - `size` holds the size (in bytes) of the data that is actually used.
*
* - `asize` holds the known total amount of allocated memory if the `ptr`
* was allocated by libgit2. It may be larger than `size`. If `ptr`
* was not allocated by libgit2 and should not be resized and/or freed,
* then `asize` will be set to zero.
*
* Some APIs may occasionally do something slightly unusual with a buffer,
* such as setting `ptr` to a value that was passed in by the user. In
* those cases, the behavior will be clearly documented by the API.
*/
typedef struct {
char *ptr;
size_t asize, size;
} git_buf;
/**
* Static initializer for git_buf from static buffer
*/
#define GIT_BUF_INIT_CONST(STR,LEN) { (char *)(STR), 0, (size_t)(LEN) }
/**
* Free the memory referred to by the git_buf.
*
* Note that this does not free the `git_buf` itself, just the memory
* pointed to by `buffer->ptr`. This will not free the memory if it looks
* like it was not allocated internally, but it will clear the buffer back
* to the empty state.
*
* @param buffer The buffer to deallocate
*/
GIT_EXTERN(void) git_buf_free(git_buf *buffer);
/**
* Resize the buffer allocation to make more space.
*
* This will attempt to grow the buffer to accomodate the target size.
*
* If the buffer refers to memory that was not allocated by libgit2 (i.e.
* the `asize` field is zero), then `ptr` will be replaced with a newly
* allocated block of data. Be careful so that memory allocated by the
* caller is not lost. As a special variant, if you pass `target_size` as
* 0 and the memory is not allocated by libgit2, this will allocate a new
* buffer of size `size` and copy the external data into it.
*
* Currently, this will never shrink a buffer, only expand it.
*
* If the allocation fails, this will return an error and the buffer will be
* marked as invalid for future operations, invaliding the contents.
*
* @param buffer The buffer to be resized; may or may not be allocated yet
* @param target_size The desired available size
* @return 0 on success, -1 on allocation failure
*/
GIT_EXTERN(int) git_buf_grow(git_buf *buffer, size_t target_size);
/**
* Set buffer to a copy of some raw data.
*
* @param buffer The buffer to set
* @param data The data to copy into the buffer
* @param datalen The length of the data to copy into the buffer
* @return 0 on success, -1 on allocation failure
*/
GIT_EXTERN(int) git_buf_set(
git_buf *buffer, const void *data, size_t datalen);
GIT_END_DECL
/** @} */
#endif

View File

@ -131,6 +131,13 @@ typedef enum {
/** Don't refresh index/config/etc before doing checkout */
GIT_CHECKOUT_NO_REFRESH = (1u << 9),
/** Allow checkout to skip unmerged files */
GIT_CHECKOUT_SKIP_UNMERGED = (1u << 10),
/** For unmerged files, checkout stage 2 from index */
GIT_CHECKOUT_USE_OURS = (1u << 11),
/** For unmerged files, checkout stage 3 from index */
GIT_CHECKOUT_USE_THEIRS = (1u << 12),
/** Treat pathspec as simple list of exact match file paths */
GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH = (1u << 13),
@ -141,13 +148,6 @@ typedef enum {
* THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
*/
/** Allow checkout to skip unmerged files (NOT IMPLEMENTED) */
GIT_CHECKOUT_SKIP_UNMERGED = (1u << 10),
/** For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED) */
GIT_CHECKOUT_USE_OURS = (1u << 11),
/** For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED) */
GIT_CHECKOUT_USE_THEIRS = (1u << 12),
/** Recursively checkout submodules with same options (NOT IMPLEMENTED) */
GIT_CHECKOUT_UPDATE_SUBMODULES = (1u << 16),
/** Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) */
@ -238,6 +238,9 @@ typedef struct git_checkout_opts {
git_tree *baseline; /** expected content of workdir, defaults to HEAD */
const char *target_directory; /** alternative checkout path to workdir */
const char *our_label; /** the name of the "our" side of conflicts */
const char *their_label; /** the name of the "their" side of conflicts */
} git_checkout_opts;
#define GIT_CHECKOUT_OPTS_VERSION 1
@ -249,13 +252,13 @@ typedef struct git_checkout_opts {
*
* @param repo repository to check out (must be non-bare)
* @param opts specifies checkout options (may be NULL)
* @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing
* @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing
* branch, GIT_ERROR otherwise (use giterr_last for information
* about the error)
*/
GIT_EXTERN(int) git_checkout_head(
git_repository *repo,
git_checkout_opts *opts);
const git_checkout_opts *opts);
/**
* Updates files in the working tree to match the content of the index.
@ -269,7 +272,7 @@ GIT_EXTERN(int) git_checkout_head(
GIT_EXTERN(int) git_checkout_index(
git_repository *repo,
git_index *index,
git_checkout_opts *opts);
const git_checkout_opts *opts);
/**
* Updates files in the index and working tree to match the content of the
@ -277,7 +280,7 @@ GIT_EXTERN(int) git_checkout_index(
*
* @param repo repository to check out (must be non-bare)
* @param treeish a commit, tag or tree which content will be used to update
* the working directory
* the working directory (or NULL to use HEAD)
* @param opts specifies checkout options (may be NULL)
* @return 0 on success, GIT_ERROR otherwise (use giterr_last for information
* about the error)
@ -285,7 +288,7 @@ GIT_EXTERN(int) git_checkout_index(
GIT_EXTERN(int) git_checkout_tree(
git_repository *repo,
const git_object *treeish,
git_checkout_opts *opts);
const git_checkout_opts *opts);
/** @} */
GIT_END_DECL

View File

@ -35,30 +35,12 @@ GIT_BEGIN_DECL
* set the `checkout_strategy` to GIT_CHECKOUT_DEFAULT.
* - `bare` should be set to zero to create a standard repo, non-zero for
* a bare repo
* - `fetch_progress_cb` is optional callback for fetch progress. Be aware that
* this is called inline with network and indexing operations, so performance
* may be affected.
* - `fetch_progress_payload` is payload for fetch_progress_cb
* - `ignore_cert_errors` should be set to 1 if errors validating the remote host's
* certificate should be ignored.
*
* ** "origin" remote options: **
* - `remote_name` is the name given to the "origin" remote. The default is
* "origin".
* - `pushurl` is a URL to be used for pushing. NULL means use the fetch url.
* - `fetch_spec` is the fetch specification to be used for fetching. NULL
* results in the same behavior as GIT_REMOTE_DEFAULT_FETCH.
* - `push_spec` is the fetch specification to be used for pushing. NULL means
* use the same spec as for fetching.
* - `cred_acquire_cb` is a callback to be used if credentials are required
* during the initial fetch.
* - `cred_acquire_payload` is the payload for the above callback.
* - `transport_flags` is flags used to create transport if no transport is
* provided.
* - `transport` is a custom transport to be used for the initial fetch. NULL
* means use the transport autodetected from the URL.
* - `remote_callbacks` may be used to specify custom progress callbacks for
* the origin remote before the fetch is initiated.
* - `remote_autotag` may be used to specify the autotag setting before the
* initial fetch. The default is GIT_REMOTE_DOWNLOAD_TAGS_ALL.
* - `checkout_branch` gives the name of the branch to checkout. NULL means
* use the remote's HEAD.
*/
@ -67,29 +49,23 @@ typedef struct git_clone_options {
unsigned int version;
git_checkout_opts checkout_opts;
int bare;
git_transfer_progress_callback fetch_progress_cb;
void *fetch_progress_payload;
git_remote_callbacks remote_callbacks;
int bare;
int ignore_cert_errors;
const char *remote_name;
const char *pushurl;
const char *fetch_spec;
const char *push_spec;
git_cred_acquire_cb cred_acquire_cb;
void *cred_acquire_payload;
git_transport_flags_t transport_flags;
git_transport *transport;
git_remote_callbacks *remote_callbacks;
git_remote_autotag_option_t remote_autotag;
const char* checkout_branch;
} git_clone_options;
#define GIT_CLONE_OPTIONS_VERSION 1
#define GIT_CLONE_OPTIONS_INIT {GIT_CLONE_OPTIONS_VERSION, {GIT_CHECKOUT_OPTS_VERSION, GIT_CHECKOUT_SAFE_CREATE}}
#define GIT_CLONE_OPTIONS_INIT {GIT_CLONE_OPTIONS_VERSION, {GIT_CHECKOUT_OPTS_VERSION, GIT_CHECKOUT_SAFE_CREATE}, GIT_REMOTE_CALLBACKS_INIT}
/**
* Clone a remote repository, and checkout the branch pointed to by the remote
* HEAD.
* Clone a remote repository.
*
* This version handles the simple case. If you'd like to create the
* repository or remote with non-default settings, you can create and
* configure them and then use `git_clone_into()`.
*
* @param out pointer that will receive the resulting repository object
* @param url the remote repository to clone
@ -105,6 +81,22 @@ GIT_EXTERN(int) git_clone(
const char *local_path,
const git_clone_options *options);
/**
* Clone into a repository
*
* After creating the repository and remote and configuring them for
* paths and callbacks respectively, you can call this function to
* perform the clone operation and optionally checkout files.
*
* @param repo the repository to use
* @param remote the remote repository to clone from
* @param co_opts options to use during checkout
* @param branch the branch to checkout after the clone, pass NULL for the remote's
* default branch
* @return 0 on success or an error code
*/
GIT_EXTERN(int) git_clone_into(git_repository *repo, git_remote *remote, const git_checkout_opts *co_opts, const char *branch);
/** @} */
GIT_END_DECL
#endif

View File

@ -24,17 +24,24 @@ GIT_BEGIN_DECL
/**
* 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 repo the repo to use when locating the commit.
* @param id identity of the commit to locate. If the object is
* an annotated tag it will be peeled back to the commit.
* @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,
* given a prefix of its identifier (short id).
* Lookup a commit object from a repository, given a prefix of its
* identifier (short id).
*
* The returned object should be released with `git_commit_free` when no
* longer needed.
*
* @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
* @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
@ -92,11 +100,22 @@ GIT_EXTERN(const char *) git_commit_message_encoding(const git_commit *commit);
/**
* Get the full message of a commit.
*
* The returned message will be slightly prettified by removing any
* potential leading newlines.
*
* @param commit a previously loaded commit.
* @return the message of a commit
*/
GIT_EXTERN(const char *) git_commit_message(const git_commit *commit);
/**
* Get the full raw message of a commit.
*
* @param commit a previously loaded commit.
* @return the raw message of a commit
*/
GIT_EXTERN(const char *) git_commit_message_raw(const git_commit *commit);
/**
* Get the commit time (i.e. committer time) of a commit.
*
@ -129,6 +148,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);
/**
* 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.
*

View File

@ -105,7 +105,8 @@ GIT_EXTERN(void) git_libgit2_version(int *major, int *minor, int *rev);
*/
typedef enum {
GIT_CAP_THREADS = ( 1 << 0 ),
GIT_CAP_HTTPS = ( 1 << 1 )
GIT_CAP_HTTPS = ( 1 << 1 ),
GIT_CAP_SSH = ( 1 << 2 ),
} git_cap_t;
/**
@ -135,7 +136,9 @@ typedef enum {
GIT_OPT_SET_CACHE_OBJECT_LIMIT,
GIT_OPT_SET_CACHE_MAX_SIZE,
GIT_OPT_ENABLE_CACHING,
GIT_OPT_GET_CACHED_MEMORY
GIT_OPT_GET_CACHED_MEMORY,
GIT_OPT_GET_TEMPLATE_PATH,
GIT_OPT_SET_TEMPLATE_PATH
} git_libgit2_opt_t;
/**
@ -209,6 +212,18 @@ typedef enum {
* > Get the current bytes in cache and the maximum that would be
* > allowed in the cache.
*
* * opts(GIT_OPT_GET_TEMPLATE_PATH, char *out, size_t len)
*
* > Get the default template path.
* > The path is written to the `out`
* > buffer up to size `len`. Returns GIT_EBUFS if buffer is too small.
*
* * opts(GIT_OPT_SET_TEMPLATE_PATH, const char *path)
*
* > Set the default template path.
* >
* > - `path` directory of template.
*
* @param option Option key
* @param ... value to set the option
* @return 0 on success, <0 on failure

View File

@ -61,6 +61,7 @@ typedef struct {
} git_config_entry;
typedef int (*git_config_foreach_cb)(const git_config_entry *, void *);
typedef struct git_config_iterator git_config_iterator;
typedef enum {
GIT_CVAR_FALSE = 0,
@ -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);
/**
* 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
*
@ -338,7 +339,34 @@ GIT_EXTERN(int) git_config_get_string(const char **out, const git_config *cfg, c
* @param callback the function to be called on each value of the variable
* @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
@ -406,6 +434,17 @@ GIT_EXTERN(int) git_config_set_multivar(git_config *cfg, const char *name, const
*/
GIT_EXTERN(int) git_config_delete_entry(git_config *cfg, const char *name);
/**
* Deletes one or several entries from a multivar in the local config file.
*
* @param cfg where to look for the variables
* @param name the variable's name
* @param regexp a regular expression to indicate which values to delete
*
* @return 0 or an error code
*/
GIT_EXTERN(int) git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp);
/**
* Perform an operation on each config variable.
*
@ -424,6 +463,29 @@ GIT_EXTERN(int) git_config_foreach(
git_config_foreach_cb callback,
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.
*
@ -535,6 +597,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);
/**
* 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
#endif

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@
#define INCLUDE_git_errors_h__
#include "common.h"
#include "buffer.h"
/**
* @file git2/errors.h
@ -27,11 +28,12 @@ typedef enum {
GIT_EBUFS = -6,
GIT_EUSER = -7,
GIT_EBAREREPO = -8,
GIT_EORPHANEDHEAD = -9,
GIT_EUNBORNBRANCH = -9,
GIT_EUNMERGED = -10,
GIT_ENONFASTFORWARD = -11,
GIT_EINVALIDSPEC = -12,
GIT_EMERGECONFLICT = -13,
GIT_ELOCKED = -14,
GIT_PASSTHROUGH = -30,
GIT_ITEROVER = -31,
@ -44,6 +46,7 @@ typedef struct {
/** Error classes */
typedef enum {
GITERR_NONE = 0,
GITERR_NOMEMORY,
GITERR_OS,
GITERR_INVALID,
@ -66,6 +69,8 @@ typedef enum {
GITERR_CHECKOUT,
GITERR_FETCHHEAD,
GITERR_MERGE,
GITERR_SSH,
GITERR_FILTER,
} git_error_t;
/**
@ -81,6 +86,18 @@ GIT_EXTERN(const git_error *) giterr_last(void);
*/
GIT_EXTERN(void) giterr_clear(void);
/**
* Get the last error data and clear it.
*
* This copies the last error into the given `git_error` struct
* and returns 0 if the copy was successful, leaving the error
* cleared as if `giterr_clear` had been called.
*
* If there was no existing error in the library, -1 will be returned
* and the contents of `cpy` will be left unmodified.
*/
GIT_EXTERN(int) giterr_detach(git_error *cpy);
/**
* Set the error message string for this thread.
*

142
include/git2/filter.h Normal file
View File

@ -0,0 +1,142 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_git_filter_h__
#define INCLUDE_git_filter_h__
#include "common.h"
#include "types.h"
#include "oid.h"
#include "buffer.h"
/**
* @file git2/filter.h
* @brief Git filter APIs
*
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
/**
* Filters are applied in one of two directions: smudging - which is
* exporting a file from the Git object database to the working directory,
* and cleaning - which is importing a file from the working directory to
* the Git object database. These values control which direction of
* change is being applied.
*/
typedef enum {
GIT_FILTER_TO_WORKTREE = 0,
GIT_FILTER_SMUDGE = GIT_FILTER_TO_WORKTREE,
GIT_FILTER_TO_ODB = 1,
GIT_FILTER_CLEAN = GIT_FILTER_TO_ODB,
} git_filter_mode_t;
/**
* A filter that can transform file data
*
* This represents a filter that can be used to transform or even replace
* file data. Libgit2 includes one built in filter and it is possible to
* write your own (see git2/sys/filter.h for information on that).
*
* The two builtin filters are:
*
* * "crlf" which uses the complex rules with the "text", "eol", and
* "crlf" file attributes to decide how to convert between LF and CRLF
* line endings
* * "ident" which replaces "$Id$" in a blob with "$Id: <blob OID>$" upon
* checkout and replaced "$Id: <anything>$" with "$Id$" on checkin.
*/
typedef struct git_filter git_filter;
/**
* List of filters to be applied
*
* This represents a list of filters to be applied to a file / blob. You
* can build the list with one call, apply it with another, and dispose it
* with a third. In typical usage, there are not many occasions where a
* git_filter_list is needed directly since the library will generally
* handle conversions for you, but it can be convenient to be able to
* build and apply the list sometimes.
*/
typedef struct git_filter_list git_filter_list;
/**
* Load the filter list for a given path.
*
* This will return 0 (success) but set the output git_filter_list to NULL
* if no filters are requested for the given file.
*
* @param filters Output newly created git_filter_list (or NULL)
* @param repo Repository object that contains `path`
* @param blob The blob to which the filter will be applied (if known)
* @param path Relative path of the file to be filtered
* @param mode Filtering direction (WT->ODB or ODB->WT)
* @return 0 on success (which could still return NULL if no filters are
* needed for the requested file), <0 on error
*/
GIT_EXTERN(int) git_filter_list_load(
git_filter_list **filters,
git_repository *repo,
git_blob *blob, /* can be NULL */
const char *path,
git_filter_mode_t mode);
/**
* Apply filter list to a data buffer.
*
* See `git2/buffer.h` for background on `git_buf` objects.
*
* If the `in` buffer holds data allocated by libgit2 (i.e. `in->asize` is
* not zero), then it will be overwritten when applying the filters. If
* not, then it will be left untouched.
*
* If there are no filters to apply (or `filters` is NULL), then the `out`
* buffer will reference the `in` buffer data (with `asize` set to zero)
* instead of allocating data. This keeps allocations to a minimum, but
* it means you have to be careful about freeing the `in` data since `out`
* may be pointing to it!
*
* @param out Buffer to store the result of the filtering
* @param filters A loaded git_filter_list (or NULL)
* @param in Buffer containing the data to filter
* @return 0 on success, an error code otherwise
*/
GIT_EXTERN(int) git_filter_list_apply_to_data(
git_buf *out,
git_filter_list *filters,
git_buf *in);
/**
* Apply filter list to the contents of a file on disk
*/
GIT_EXTERN(int) git_filter_list_apply_to_file(
git_buf *out,
git_filter_list *filters,
git_repository *repo,
const char *path);
/**
* Apply filter list to the contents of a blob
*/
GIT_EXTERN(int) git_filter_list_apply_to_blob(
git_buf *out,
git_filter_list *filters,
git_blob *blob);
/**
* Free a git_filter_list
*
* @param filters A git_filter_list created by `git_filter_list_load`
*/
GIT_EXTERN(void) git_filter_list_free(git_filter_list *filters);
GIT_END_DECL
/** @} */
#endif

View File

@ -120,9 +120,9 @@ typedef struct git_index_entry {
/** Capabilities of system that affect index actions. */
typedef enum {
GIT_INDEXCAP_IGNORE_CASE = 1,
GIT_INDEXCAP_NO_FILEMODE = 2,
GIT_INDEXCAP_NO_SYMLINKS = 4,
GIT_INDEXCAP_IGNORE_CASE = 1u,
GIT_INDEXCAP_NO_FILEMODE = 2u,
GIT_INDEXCAP_NO_SYMLINKS = 4u,
GIT_INDEXCAP_FROM_OWNER = ~0u
} git_indexcap_t;
@ -138,6 +138,14 @@ typedef enum {
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
*
* These functions work on the index file itself.
@ -214,13 +222,23 @@ GIT_EXTERN(unsigned int) git_index_caps(const git_index *index);
GIT_EXTERN(int) git_index_set_caps(git_index *index, unsigned int caps);
/**
* Update the contents of an existing index object in memory
* by reading from the hard disk.
* Update the contents of an existing index object in memory by reading
* from the hard disk.
*
* If `force` is true, this performs a "hard" read that discards in-memory
* changes and always reloads the on-disk index data. If there is no
* on-disk version, the index will be cleared.
*
* If `force` is false, this does a "soft" read that reloads the index
* data from disk only if it has changed since the last time it was
* loaded. Purely in-memory index data will be untouched. Be aware: if
* there are changes on disk, unwritten in-memory changes are discarded.
*
* @param index an existing index object
* @param force if true, always reload, vs. only read if file has changed
* @return 0 or an error code
*/
GIT_EXTERN(int) git_index_read(git_index *index);
GIT_EXTERN(int) git_index_read(git_index *index, int force);
/**
* Write an existing index object from memory back to disk
@ -231,6 +249,14 @@ GIT_EXTERN(int) git_index_read(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
*

View File

@ -13,19 +13,25 @@
GIT_BEGIN_DECL
typedef struct git_indexer_stream git_indexer_stream;
typedef struct git_indexer git_indexer;
/**
* Create a new streaming indexer instance
* Create a new indexer instance
*
* @param out where to store the indexer instance
* @param path to the directory where the packfile should be stored
* @param mode permissions to use creating packfile or 0 for defaults
* @param odb object database from which to read base objects when
* fixing thin packs. Pass NULL if no thin pack is expected (an error
* will be returned if there are bases missing)
* @param progress_cb function to call with progress information
* @param progress_cb_payload payload for the progress callback
*/
GIT_EXTERN(int) git_indexer_stream_new(
git_indexer_stream **out,
GIT_EXTERN(int) git_indexer_new(
git_indexer **out,
const char *path,
unsigned int mode,
git_odb *odb,
git_transfer_progress_callback progress_cb,
void *progress_cb_payload);
@ -37,7 +43,7 @@ GIT_EXTERN(int) git_indexer_stream_new(
* @param size the size of the data in bytes
* @param stats stat storage
*/
GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_transfer_progress *stats);
GIT_EXTERN(int) git_indexer_append(git_indexer *idx, const void *data, size_t size, git_transfer_progress *stats);
/**
* Finalize the pack and index
@ -46,7 +52,7 @@ GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data
*
* @param idx the indexer
*/
GIT_EXTERN(int) git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *stats);
GIT_EXTERN(int) git_indexer_commit(git_indexer *idx, git_transfer_progress *stats);
/**
* Get the packfile's hash
@ -56,14 +62,14 @@ GIT_EXTERN(int) git_indexer_stream_finalize(git_indexer_stream *idx, git_transfe
*
* @param idx the indexer instance
*/
GIT_EXTERN(const git_oid *) git_indexer_stream_hash(const git_indexer_stream *idx);
GIT_EXTERN(const git_oid *) git_indexer_hash(const git_indexer *idx);
/**
* Free the indexer and its resources
*
* @param idx the indexer to free
*/
GIT_EXTERN(void) git_indexer_stream_free(git_indexer_stream *idx);
GIT_EXTERN(void) git_indexer_free(git_indexer *idx);
GIT_END_DECL

View File

@ -7,11 +7,11 @@
#ifndef INCLUDE_git_merge_h__
#define INCLUDE_git_merge_h__
#include "git2/common.h"
#include "git2/types.h"
#include "git2/oid.h"
#include "git2/checkout.h"
#include "git2/index.h"
#include "common.h"
#include "types.h"
#include "oid.h"
#include "checkout.h"
#include "index.h"
/**
* @file git2/merge.h
@ -65,6 +65,29 @@ typedef struct {
#define GIT_MERGE_TREE_OPTS_INIT {GIT_MERGE_TREE_OPTS_VERSION}
/**
* Option flags for `git_merge`.
*
* GIT_MERGE_NO_FASTFORWARD - Do not fast-forward.
*/
typedef enum {
GIT_MERGE_NO_FASTFORWARD = 1,
GIT_MERGE_FASTFORWARD_ONLY = 2,
} git_merge_flags_t;
typedef struct {
unsigned int version;
git_merge_flags_t merge_flags;
git_merge_tree_opts merge_tree_opts;
git_checkout_opts checkout_opts;
} git_merge_opts;
#define GIT_MERGE_OPTS_VERSION 1
#define GIT_MERGE_OPTS_INIT {GIT_MERGE_OPTS_VERSION, 0, GIT_MERGE_TREE_OPTS_INIT, GIT_CHECKOUT_OPTS_INIT}
/**
* Find a merge base between two commits
*
@ -85,15 +108,15 @@ GIT_EXTERN(int) git_merge_base(
*
* @param out the OID of a merge base considering all the commits
* @param repo the repository where the commits exist
* @param input_array oids of the commits
* @param length The number of commits in the provided `input_array`
* @param input_array oids of the commits
* @return Zero on success; GIT_ENOTFOUND or -1 on failure.
*/
GIT_EXTERN(int) git_merge_base_many(
git_oid *out,
git_repository *repo,
const git_oid input_array[],
size_t length);
size_t length,
const git_oid input_array[]);
/**
* Creates a `git_merge_head` from the given reference
@ -168,6 +191,43 @@ GIT_EXTERN(int) git_merge_trees(
const git_tree *their_tree,
const git_merge_tree_opts *opts);
/**
* Merges the given commits into HEAD, producing a new commit.
*
* @param out the results of the merge
* @param repo the repository to merge
* @param merge_heads the heads to merge into
* @param merge_heads_len the number of heads to merge
* @param flags merge flags
*/
GIT_EXTERN(int) git_merge(
git_merge_result **out,
git_repository *repo,
const git_merge_head **their_heads,
size_t their_heads_len,
const git_merge_opts *opts);
/**
* Returns true if a merge is up-to-date (we were asked to merge the target
* into itself.)
*/
GIT_EXTERN(int) git_merge_result_is_uptodate(git_merge_result *merge_result);
/**
* Returns true if a merge is eligible for fastforward
*/
GIT_EXTERN(int) git_merge_result_is_fastforward(git_merge_result *merge_result);
/**
* Gets the fast-forward OID if the merge was a fastforward.
*
* @param out the OID of the fast-forward
* @param merge_result the results of the merge
*/
GIT_EXTERN(int) git_merge_result_fastforward_oid(git_oid *out, git_merge_result *merge_result);
GIT_EXTERN(void) git_merge_result_free(git_merge_result *merge_result);
/** @} */
GIT_END_DECL
#endif

View File

@ -99,7 +99,7 @@ GIT_EXTERN(int) git_note_read(
/**
* Get the note message
*
* @param note
* @param note the note
* @return the note message
*/
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
*
* @param note
* @param note the note
* @return the note object OID
*/
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 id the unique identifier for 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_object **object,
@ -78,6 +78,23 @@ GIT_EXTERN(int) git_object_lookup_prefix(
size_t len,
git_otype type);
/**
* Lookup an object that represents a tree entry.
*
* @param out buffer that receives a pointer to the object (which must be freed
* by the caller)
* @param treeish root object that can be peeled to a tree
* @param path relative path from the root object to the desired object
* @param type type of object desired
* @return 0 on success, or an error code
*/
GIT_EXTERN(int) git_object_lookup_bypath(
git_object **out,
const git_object *treeish,
const char *path,
git_otype type);
/**
* Get the id (SHA1) of a repository 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
* when opening the stream.
*
* The returned stream will be of type `GIT_STREAM_WRONLY` and
* will have the following methods:
* The returned stream will be of type `GIT_STREAM_WRONLY`, and it
* 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
* - stream->finalize_write: close the stream and store the object in
* 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.
* The stream must always be freed when done with `git_odb_stream_free` or
* will leak memory.
*
* @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);
/**
* 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
*
@ -321,6 +357,20 @@ GIT_EXTERN(int) git_odb_hash(git_oid *out, const void *data, size_t len, git_oty
*/
GIT_EXTERN(int) git_odb_hashfile(git_oid *out, const char *path, git_otype type);
/**
* Create a copy of an odb_object
*
* The returned copy must be manually freed with `git_odb_object_free`.
* Note that because of an implementation detail, the returned copy will be
* the same pointer as `source`: the object is internally refcounted, so the
* copy still needs to be freed twice.
*
* @param dest pointer where to store the copy
* @param source object to copy
* @return 0 or an error code
*/
GIT_EXTERN(int) git_odb_object_dup(git_odb_object **dest, git_odb_object *source);
/**
* Close an ODB object
*

View File

@ -7,8 +7,8 @@
#ifndef INCLUDE_git_odb_backend_h__
#define INCLUDE_git_odb_backend_h__
#include "git2/common.h"
#include "git2/types.h"
#include "common.h"
#include "types.h"
/**
* @file git2/backend.h
@ -40,10 +40,18 @@ GIT_EXTERN(int) git_odb_backend_pack(git_odb_backend **out, const char *objects_
* @param objects_dir the Git repository's objects directory
* @param compression_level zlib compression level to use
* @param do_fsync whether to do an fsync() after writing (currently ignored)
* @param dir_mode permissions to use creating a directory or 0 for defaults
* @param file_mode permissions to use creating a file or 0 for defaults
*
* @return 0 or an error code
*/
GIT_EXTERN(int) git_odb_backend_loose(git_odb_backend **out, const char *objects_dir, int compression_level, int do_fsync);
GIT_EXTERN(int) git_odb_backend_loose(
git_odb_backend **out,
const char *objects_dir,
int compression_level,
int do_fsync,
unsigned int dir_mode,
unsigned int file_mode);
/**
* Create a backend out of a single packfile
@ -65,14 +73,50 @@ typedef enum {
GIT_STREAM_RW = (GIT_STREAM_RDONLY | GIT_STREAM_WRONLY),
} 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 {
git_odb_backend *backend;
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);
/**
* Write `len` bytes from `buffer` into the stream.
*/
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);
};
@ -80,7 +124,7 @@ struct git_odb_stream {
struct git_odb_writepack {
git_odb_backend *backend;
int (*add)(git_odb_writepack *writepack, const void *data, size_t size, git_transfer_progress *stats);
int (*append)(git_odb_writepack *writepack, const void *data, size_t size, git_transfer_progress *stats);
int (*commit)(git_odb_writepack *writepack, git_transfer_progress *stats);
void (*free)(git_odb_writepack *writepack);
};

View File

@ -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 str input hex string of an object id.
* @return GIT_ENOTOID if str is not a valid hex string,
* 0 in case of a match, GIT_ERROR otherwise.
* @return 0 in case of a match, -1 otherwise.
*/
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.
*
* 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
* for any kind of program, and keeps the algorithm fast and
* memory-efficient.
*
* 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 text_id an OID in text form

View File

@ -38,13 +38,21 @@
* `git_packbuilder_set_threads` can be used to adjust the number of
* threads used for the process.
*
* See tests-clar/pack/packbuilder.c for an example.
* See tests/pack/packbuilder.c for an example.
*
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
/**
* Stages that are reported by the packbuilder progress callback.
*/
typedef enum {
GIT_PACKBUILDER_ADDING_OBJECTS = 0,
GIT_PACKBUILDER_DELTAFICATION = 1,
} git_packbuilder_stage_t;
/**
* Initialize a new packbuilder
*
@ -111,6 +119,7 @@ GIT_EXTERN(int) git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid
*
* @param pb The packbuilder
* @param path to the directory where the packfile and index should be stored
* @param mode permissions to use creating a packfile or 0 for defaults
* @param progress_cb function to call with progress information from the indexer (optional)
* @param progress_cb_payload payload for the progress callback (optional)
*
@ -119,9 +128,20 @@ GIT_EXTERN(int) git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid
GIT_EXTERN(int) git_packbuilder_write(
git_packbuilder *pb,
const char *path,
unsigned int mode,
git_transfer_progress_callback progress_cb,
void *progress_cb_payload);
/**
* Get the packfile's hash
*
* A packfile's name is derived from the sorted hashing of all object
* names. This is only correct after the packfile has been written.
*
* @param pb The packbuilder object
*/
GIT_EXTERN(const git_oid *) git_packbuilder_hash(git_packbuilder *pb);
typedef int (*git_packbuilder_foreach_cb)(void *buf, size_t size, void *payload);
/**
* Create the new pack and pass each object to the callback
@ -137,7 +157,7 @@ GIT_EXTERN(int) git_packbuilder_foreach(git_packbuilder *pb, git_packbuilder_for
* Get the total number of objects the packbuilder will write out
*
* @param pb the packbuilder
* @return
* @return the number of objects in the packfile
*/
GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb);
@ -145,10 +165,32 @@ GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb);
* Get the number of objects the packbuilder has already written out
*
* @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);
/** Packbuilder progress notification function */
typedef int (*git_packbuilder_progress)(
int stage,
unsigned int current,
unsigned int total,
void *payload);
/**
* Set the callbacks for a packbuilder
*
* @param pb The packbuilder object
* @param progress_cb Function to call with progress information during
* pack building. Be aware that this is called inline with pack building
* operations, so performance may be affected.
* @param progress_cb_payload Payload for progress callback.
* @return 0 or an error code
*/
GIT_EXTERN(int) git_packbuilder_set_callbacks(
git_packbuilder *pb,
git_packbuilder_progress progress_cb,
void *progress_cb_payload);
/**
* Free the packbuilder and all associated data
*

250
include/git2/patch.h Normal file
View File

@ -0,0 +1,250 @@
/*
* 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_patch_h__
#define INCLUDE_git_patch_h__
#include "common.h"
#include "types.h"
#include "oid.h"
#include "diff.h"
/**
* @file git2/patch.h
* @brief Patch handling routines.
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
/**
* The diff patch is used to store all the text diffs for a delta.
*
* You can easily loop over the content of patches and get information about
* them.
*/
typedef struct git_patch git_patch;
/**
* Return the diff delta and patch for an entry in the diff list.
*
* The `git_patch` is a newly created object contains the text diffs
* for the delta. You have to call `git_patch_free()` when you are
* done with it. You can use the patch object to loop over all the hunks
* and lines in the diff of the one delta.
*
* For an unchanged file or a binary file, no `git_patch` will be
* created, the output will be set to NULL, and the `binary` flag will be
* set true in the `git_diff_delta` structure.
*
* The `git_diff_delta` pointer points to internal data and you do not have
* to release it when you are done with it. It will go away when the
* `git_diff` and `git_patch` go away.
*
* It is okay to pass NULL for either of the output parameters; if you pass
* NULL for the `git_patch`, then the text diff will not be calculated.
*
* @param out Output parameter for the delta patch object
* @param diff Diff list object
* @param idx Index into diff list
* @return 0 on success, other value < 0 on error
*/
GIT_EXTERN(int) git_patch_from_diff(
git_patch **out, git_diff *diff, size_t idx);
/**
* Directly generate a patch from the difference between two blobs.
*
* This is just like `git_diff_blobs()` except it generates a patch object
* for the difference instead of directly making callbacks. You can use the
* standard `git_patch` accessor functions to read the patch data, and
* you must call `git_patch_free()` on the patch when done.
*
* @param out The generated patch; NULL on error
* @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_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
*/
GIT_EXTERN(int) git_patch_from_blobs(
git_patch **out,
const git_blob *old_blob,
const char *old_as_path,
const git_blob *new_blob,
const char *new_as_path,
const git_diff_options *opts);
/**
* Directly generate a patch from the difference between a blob and a buffer.
*
* This is just like `git_diff_blob_to_buffer()` except it generates a patch
* object for the difference instead of directly making callbacks. You can
* use the standard `git_patch` accessor functions to read the patch
* data, and you must call `git_patch_free()` on the patch when done.
*
* @param out The generated patch; NULL on error
* @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_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 opts Options for diff, or NULL for default options
* @return 0 on success or error code < 0
*/
GIT_EXTERN(int) git_patch_from_blob_and_buffer(
git_patch **out,
const git_blob *old_blob,
const char *old_as_path,
const char *buffer,
size_t buffer_len,
const char *buffer_as_path,
const git_diff_options *opts);
/**
* Free a git_patch object.
*/
GIT_EXTERN(void) git_patch_free(git_patch *patch);
/**
* Get the delta associated with a patch
*/
GIT_EXTERN(const git_diff_delta *) git_patch_get_delta(git_patch *patch);
/**
* Get the number of hunks in a patch
*/
GIT_EXTERN(size_t) git_patch_num_hunks(git_patch *patch);
/**
* Get line counts of each type in a patch.
*
* This helps imitate a diff --numstat type of output. For that purpose,
* you only need the `total_additions` and `total_deletions` values, but we
* include the `total_context` line count in case you want the total number
* of lines of diff output that will be generated.
*
* All outputs are optional. Pass NULL if you don't need a particular count.
*
* @param total_context Count of context lines in output, can be NULL.
* @param total_additions Count of addition lines in output, can be NULL.
* @param total_deletions Count of deletion lines in output, can be NULL.
* @param patch The git_patch object
* @return 0 on success, <0 on error
*/
GIT_EXTERN(int) git_patch_line_stats(
size_t *total_context,
size_t *total_additions,
size_t *total_deletions,
const git_patch *patch);
/**
* Get the information about a hunk in a patch
*
* Given a patch and a hunk index into the patch, this returns detailed
* information about that hunk. Any of the output pointers can be passed
* as NULL if you don't care about that particular piece of information.
*
* @param out Output pointer to git_diff_hunk of hunk
* @param lines_in_hunk Output count of total lines in this hunk
* @param patch Input pointer to patch object
* @param hunk_idx Input index of hunk to get information about
* @return 0 on success, GIT_ENOTFOUND if hunk_idx out of range, <0 on error
*/
GIT_EXTERN(int) git_patch_get_hunk(
const git_diff_hunk **out,
size_t *lines_in_hunk,
git_patch *patch,
size_t hunk_idx);
/**
* Get the number of lines in a hunk.
*
* @param patch The git_patch object
* @param hunk_idx Index of the hunk
* @return Number of lines in hunk or -1 if invalid hunk index
*/
GIT_EXTERN(int) git_patch_num_lines_in_hunk(
git_patch *patch,
size_t hunk_idx);
/**
* Get data about a line in a hunk of a patch.
*
* Given a patch, a hunk index, and a line index in the hunk, this
* will return a lot of details about that line. If you pass a hunk
* index larger than the number of hunks or a line index larger than
* the number of lines in the hunk, this will return -1.
*
* @param out The git_diff_line data for this line
* @param patch The patch to look in
* @param hunk_idx The index of the hunk
* @param line_of_hunk The index of the line in the hunk
* @return 0 on success, <0 on failure
*/
GIT_EXTERN(int) git_patch_get_line_in_hunk(
const git_diff_line **out,
git_patch *patch,
size_t hunk_idx,
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_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_patch_size(
git_patch *patch,
int include_context,
int include_hunk_headers,
int include_file_headers);
/**
* Serialize the patch to text via callback.
*
* Returning a non-zero value from the callback will terminate the iteration
* and cause this return `GIT_EUSER`.
*
* @param patch A git_patch representing changes to one file
* @param print_cb Callback function to output lines of the patch. Will be
* called for file headers, hunk headers, and diff lines.
* @param payload Reference pointer that will be passed to your callbacks.
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/
GIT_EXTERN(int) git_patch_print(
git_patch *patch,
git_diff_line_cb print_cb,
void *payload);
/**
* Get the content of a patch as a single diff text.
*
* @param string Allocated string; caller must free.
* @param patch A git_patch representing changes to one file
* @return 0 on success, <0 on failure.
*/
GIT_EXTERN(int) git_patch_to_str(
char **string,
git_patch *patch);
GIT_END_DECL
/**@}*/
#endif

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 *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

@ -8,6 +8,7 @@
#define INCLUDE_git_push_h__
#include "common.h"
#include "pack.h"
/**
* @file git2/push.h
@ -38,6 +39,13 @@ typedef struct {
#define GIT_PUSH_OPTIONS_VERSION 1
#define GIT_PUSH_OPTIONS_INIT { GIT_PUSH_OPTIONS_VERSION }
/** Push network progress notification function */
typedef int (*git_push_transfer_progress)(
unsigned int current,
unsigned int total,
size_t bytes,
void* payload);
/**
* Create a new push object
*
@ -60,6 +68,27 @@ GIT_EXTERN(int) git_push_set_options(
git_push *push,
const git_push_options *opts);
/**
* Set the callbacks for a push
*
* @param push The push object
* @param pack_progress_cb Function to call with progress information during
* pack building. Be aware that this is called inline with pack building
* operations, so performance may be affected.
* @param pack_progress_cb_payload Payload for the pack progress callback.
* @param transfer_progress_cb Function to call with progress information during
* the upload portion of a push. Be aware that this is called inline with
* pack building operations, so performance may be affected.
* @param transfer_progress_cb_payload Payload for the network progress callback.
* @return 0 or an error code
*/
GIT_EXTERN(int) git_push_set_callbacks(
git_push *push,
git_packbuilder_progress pack_progress_cb,
void *pack_progress_cb_payload,
git_push_transfer_progress transfer_progress_cb,
void *transfer_progress_cb_payload);
/**
* Add a refspec to be pushed
*
@ -98,7 +127,7 @@ GIT_EXTERN(int) git_push_finish(git_push *push);
*
* @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);

View File

@ -31,10 +31,11 @@ GIT_BEGIN_DECL
* git_reflog_free().
*
* @param out pointer to reflog
* @param ref reference to read the reflog for
* @param repo the repostiory
* @param name reference to look up
* @return 0 or an error code
*/
GIT_EXTERN(int) git_reflog_read(git_reflog **out, const git_reference *ref);
GIT_EXTERN(int) git_reflog_read(git_reflog **out, git_repository *repo, const char *name);
/**
* Write an existing in-memory reflog object back to disk
@ -59,26 +60,45 @@ GIT_EXTERN(int) git_reflog_write(git_reflog *reflog);
GIT_EXTERN(int) git_reflog_append(git_reflog *reflog, const git_oid *id, const git_signature *committer, const char *msg);
/**
* Rename the reflog for the given reference
* Add a new entry to the named reflog.
*
* This utility function loads the named reflog, appends to it and
* writes it back out to the backend.
*
* `msg` is optional and can be NULL.
*
* @param repo the repository to act on
* @param name the reflog's name
* @param id the OID the reference is now pointing to
* @param committer the signature of the committer
* @param msg the reflog message
* @return 0 or an error code
*/
GIT_EXTERN(int) git_reflog_append_to(git_repository *repo, const char *name, const git_oid *id, const git_signature *committer, const char *msg);
/**
* Rename a reflog
*
* The reflog to be renamed is expected to already exist
*
* The new name will be checked for validity.
* See `git_reference_create_symbolic()` for rules about valid names.
*
* @param ref the reference
* @param name the new name of the reference
* @param repo the repository
* @param old_name the old name of the reference
* @param new_name the new name of the reference
* @return 0 on success, GIT_EINVALIDSPEC or an error code
*/
GIT_EXTERN(int) git_reflog_rename(git_reference *ref, const char *name);
GIT_EXTERN(int) git_reflog_rename(git_repository *repo, const char *old_name, const char *name);
/**
* Delete the reflog for the given reference
*
* @param ref the reference
* @param repo the repository
* @param name the reflog to delete
* @return 0 or an error code
*/
GIT_EXTERN(int) git_reflog_delete(git_reference *ref);
GIT_EXTERN(int) git_reflog_delete(git_repository *repo, const char *name);
/**
* Get the number of log entries in a reflog

View File

@ -32,7 +32,7 @@ GIT_BEGIN_DECL
* @param out pointer to the looked-up 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, ...)
* @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);
@ -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 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, ...)
* @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_oid *out, git_repository *repo, const char *name);
@ -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 target The target of the reference
* @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);
@ -126,7 +126,7 @@ GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repositor
* @param name The name of the reference
* @param id The object id pointed to by the reference.
* @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);
@ -225,7 +225,7 @@ GIT_EXTERN(git_repository *) git_reference_owner(const git_reference *ref);
* @param out Pointer to the newly created reference
* @param ref 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_reference **out,
@ -268,7 +268,7 @@ GIT_EXTERN(int) git_reference_set_target(
* @param ref The reference to rename
* @param new_name The new name for the 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(
@ -442,9 +442,18 @@ GIT_EXTERN(int) git_reference_is_branch(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 {
GIT_REF_FORMAT_NORMAL = 0,
GIT_REF_FORMAT_NORMAL = 0u,
/**
* Control whether one-level refnames are accepted
@ -452,7 +461,7 @@ typedef enum {
* components). Those are expected to be written only using
* uppercase letters and underscore (FETCH_HEAD, ...)
*/
GIT_REF_FORMAT_ALLOW_ONELEVEL = (1 << 0),
GIT_REF_FORMAT_ALLOW_ONELEVEL = (1u << 0),
/**
* Interpret the provided name as a reference pattern for a
@ -461,14 +470,14 @@ typedef enum {
* in place of a one full pathname component
* (e.g., foo/<star>/bar but not foo/bar<star>).
*/
GIT_REF_FORMAT_REFSPEC_PATTERN = (1 << 1),
GIT_REF_FORMAT_REFSPEC_PATTERN = (1u << 1),
/**
* Interpret the name as part of a refspec in shorthand form
* so the `ONELEVEL` naming rules aren't enforced and 'master'
* becomes a valid name.
*/
GIT_REF_FORMAT_REFSPEC_SHORTHAND = (1 << 2),
GIT_REF_FORMAT_REFSPEC_SHORTHAND = (1u << 2),
} git_reference_normalize_t;
/**
@ -488,7 +497,7 @@ typedef enum {
* @param name Reference name to be checked.
* @param flags Flags to constrain name validation rules - see the
* 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.
*/
GIT_EXTERN(int) git_reference_normalize_name(

View File

@ -25,13 +25,6 @@
GIT_BEGIN_DECL
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
@ -49,6 +42,25 @@ GIT_EXTERN(int) git_remote_create(
const char *name,
const char *url);
/**
* Add a remote with the provided fetch refspec (or default if NULL) to the repository's
* configuration. This
* calls git_remote_save before returning.
*
* @param out the resulting remote
* @param repo the repository in which to create the remote
* @param name the remote's name
* @param url the remote's url
* @param fetch the remote fetch value
* @return 0, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code
*/
GIT_EXTERN(int) git_remote_create_with_fetchspec(
git_remote **out,
git_repository *repo,
const char *name,
const char *url,
const char *fetch);
/**
* Create a remote in memory
*
@ -61,7 +73,7 @@ GIT_EXTERN(int) git_remote_create(
*
* @param out pointer to the new remote object
* @param repo the associated repository
* @param fetch the fetch refspec to use for this remote. May be NULL for defaults.
* @param fetch the fetch refspec to use for this remote.
* @param url the remote repository's URL
* @return 0 or an error code
*/
@ -95,6 +107,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);
/**
* 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
*
@ -144,8 +164,11 @@ GIT_EXTERN(int) git_remote_set_pushurl(git_remote *remote, const char* url);
/**
* Add a fetch refspec to the remote
*
* Convenience function for adding a single fetch refspec to the
* current list in the remote.
*
* @param remote the remote
* @apram refspec the new fetch refspec
* @param refspec the new fetch refspec
* @return 0 or an error value
*/
GIT_EXTERN(int) git_remote_add_fetch(git_remote *remote, const char *refspec);
@ -161,9 +184,22 @@ GIT_EXTERN(int) git_remote_add_fetch(git_remote *remote, const char *refspec);
*/
GIT_EXTERN(int) git_remote_get_fetch_refspecs(git_strarray *array, git_remote *remote);
/**
* Set the remote's list of fetch refspecs
*
* The contents of the string array are copied.
*
* @param remote the remote to modify
* @param array the new list of fetch resfpecs
*/
GIT_EXTERN(int) git_remote_set_fetch_refspecs(git_remote *remote, git_strarray *array);
/**
* Add a push refspec to the remote
*
* Convenience function for adding a single push refspec to the
* current list in the remote.
*
* @param remote the remote
* @param refspec the new push refspec
* @return 0 or an error value
@ -181,6 +217,16 @@ GIT_EXTERN(int) git_remote_add_push(git_remote *remote, const char *refspec);
*/
GIT_EXTERN(int) git_remote_get_push_refspecs(git_strarray *array, git_remote *remote);
/**
* Set the remote's list of push refspecs
*
* The contents of the string array are copied.
*
* @param remote the remote to modify
* @param array the new list of push resfpecs
*/
GIT_EXTERN(int) git_remote_set_push_refspecs(git_remote *remote, git_strarray *array);
/**
* Clear the refspecs
*
@ -207,15 +253,6 @@ GIT_EXTERN(size_t) git_remote_refspec_count(git_remote *remote);
*/
GIT_EXTERN(const git_refspec *)git_remote_get_refspec(git_remote *remote, size_t n);
/**
* Remove a refspec from the remote
*
* @param remote the remote to query
* @param n the refspec to remove
* @return 0 or GIT_ENOTFOUND
*/
GIT_EXTERN(int) git_remote_remove_refspec(git_remote *remote, size_t n);
/**
* Open a connection to a remote
*
@ -236,36 +273,30 @@ GIT_EXTERN(int) git_remote_connect(git_remote *remote, git_direction direction);
* The remote (or more exactly its transport) must be connected. The
* memory belongs to the remote.
*
* If you a return a non-zero value from the callback, this will stop
* looping over the refs.
* The array will stay valid as long as the remote object exists and
* its transport isn't changed, but a copy is recommended for usage of
* the data.
*
* @param out pointer to the array
* @param size the number of remote heads
* @param remote the remote
* @param list_cb function to call with each ref discovered at the remote
* @param payload additional data to pass to the callback
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
* @return 0 on success, or an error code
*/
GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload);
GIT_EXTERN(int) git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote);
/**
* Download the packfile
* Download and index the packfile
*
* Negotiate what objects should be downloaded and download the
* packfile with those objects. The packfile is downloaded with a
* temporary filename, as it's final name is not known yet. If there
* was no packfile needed (all the objects were available locally),
* filename will be NULL and the function will return success.
* Connect to the remote if it hasn't been done yet, negotiate with
* the remote git which objects are missing, download and index the
* packfile.
*
* 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 progress_cb function to call with progress information. Be aware that
* this is called inline with network and indexing operations, so performance
* may be affected.
* @param payload payload for the progress callback
* @return 0 or an error code
*/
GIT_EXTERN(int) git_remote_download(
git_remote *remote,
git_transfer_progress_callback progress_cb,
void *payload);
GIT_EXTERN(int) git_remote_download(git_remote *remote);
/**
* Check whether the remote is connected
@ -316,6 +347,17 @@ GIT_EXTERN(void) git_remote_free(git_remote *remote);
*/
GIT_EXTERN(int) git_remote_update_tips(git_remote *remote);
/**
* Download new data and update tips
*
* Convenience function to connect to a remote, download the data,
* disconnect and update the remote-tracking branches.
*
* @param remote the remote to fetch from
* @return 0 or an error code
*/
GIT_EXTERN(int) git_remote_fetch(git_remote *remote);
/**
* Return whether a string is a valid remote URL
*
@ -351,21 +393,6 @@ GIT_EXTERN(int) git_remote_list(git_strarray *out, git_repository *repo);
*/
GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check);
/**
* Set a credentials acquisition callback for this remote. If the remote is
* not available for anonymous access, then you must set this callback in order
* to provide credentials to the transport at the time of authentication
* failure so that retry can be performed.
*
* @param remote the remote to configure
* @param cred_acquire_cb The credentials acquisition callback to use (defaults
* to NULL)
*/
GIT_EXTERN(void) git_remote_set_cred_acquire_cb(
git_remote *remote,
git_cred_acquire_cb cred_acquire_cb,
void *payload);
/**
* Sets a custom transport for the remote. The caller can use this function
* to bypass the automatic discovery of a transport by URL scheme (i.e.
@ -395,13 +422,47 @@ typedef enum git_remote_completion_type {
/**
* The callback settings structure
*
* Set the calbacks to be called by the remote.
* Set the callbacks to be called by the remote when informing the user
* about the progress of the network operations.
*/
struct git_remote_callbacks {
unsigned int version;
void (*progress)(const char *str, int len, void *data);
/**
* Textual progress from the remote. Text send over the
* progress side-band will be passed to this function (this is
* the 'counting objects' output.
*/
int (*progress)(const char *str, int len, void *data);
/**
* Completion is called when different parts of the download
* process are done (currently unused).
*/
int (*completion)(git_remote_completion_type type, void *data);
/**
* This will be called if the remote host requires
* authentication in order to connect to it.
*/
int (*credentials)(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *data);
/**
* During the download of new data, this will be regularly
* called with the current count of progress done by the
* indexer.
*/
int (*transfer_progress)(const git_transfer_progress *stats, void *data);
/**
* Each time a reference is updated locally, this function
* will be called with information about it.
*/
int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data);
/**
* This will be passed to each of the callbacks in this struct
* as the last parameter.
*/
void *payload;
};
@ -418,7 +479,7 @@ struct git_remote_callbacks {
* @param callbacks a pointer to the user's callback settings
* @return 0 or an error code
*/
GIT_EXTERN(int) git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks);
GIT_EXTERN(int) git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *callbacks);
/**
* Get the statistics structure that is filled in by the fetch operation.

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
* directory "/home/user/source/" will not return "/.git/" as the found
* 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 {
GIT_REPOSITORY_OPEN_NO_SEARCH = (1 << 0),
GIT_REPOSITORY_OPEN_CROSS_FS = (1 << 1),
GIT_REPOSITORY_OPEN_BARE = (1 << 2),
} 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:
*
* * 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.
* * NO_DOTGIT_DIR - Normally a "/.git/" will be appended to the repo
* path for non-bare repos (if it is not already there), but
@ -293,7 +297,7 @@ GIT_EXTERN(int) git_repository_init_ext(
* @param out pointer to the reference which will be retrieved
* @param repo a repository object
*
* @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing
* @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing
* branch, GIT_ENOTFOUND when HEAD is missing; an error code otherwise
*/
GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo);
@ -311,16 +315,16 @@ GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo);
GIT_EXTERN(int) git_repository_head_detached(git_repository *repo);
/**
* Check if the current branch is an orphan
* Check if the current branch is unborn
*
* An orphan branch is one named from HEAD but which doesn't exist in
* An unborn branch is one named from HEAD but which doesn't exist in
* the refs namespace, because it doesn't have any commit to point to.
*
* @param repo Repo to test
* @return 1 if the current branch is an orphan, 0 if it's not; error
* @return 1 if the current branch is unborn, 0 if it's not; error
* code if there was an error
*/
GIT_EXTERN(int) git_repository_head_orphan(git_repository *repo);
GIT_EXTERN(int) git_repository_head_unborn(git_repository *repo);
/**
* Check if a repository is empty
@ -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 len Length of `out` buffer in bytes
* @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
*/
GIT_EXTERN(int) git_repository_message(char *out, size_t len, git_repository *repo);
@ -607,7 +611,7 @@ GIT_EXTERN(int) git_repository_set_head_detached(
* Otherwise, the HEAD will be detached and point to the peeled Commit.
*
* @param repo Repository pointer
* @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing
* @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing
* branch or an error code
*/
GIT_EXTERN(int) git_repository_detach_head(

View File

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

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);
/**
* 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

View File

@ -57,7 +57,7 @@ typedef enum {
GIT_EXTERN(int) git_stash_save(
git_oid *out,
git_repository *repo,
git_signature *stasher,
const git_signature *stasher,
const char *message,
unsigned int flags);

View File

@ -60,25 +60,24 @@ typedef int (*git_status_cb)(
const char *path, unsigned int status_flags, void *payload);
/**
* For extended status, select the files on which to report status.
* Select the files on which to report status.
*
* With `git_status_foreach_ext`, this will control which changes get
* callbacks. With `git_status_list_new`, these will control which
* changes are included in the list.
*
* - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This roughly
* matches `git status --porcelain` where each file gets a callback
* indicating its status in the index and in the working directory.
* matches `git status --porcelain` regarding which files are
* included and in what order.
* - GIT_STATUS_SHOW_INDEX_ONLY only gives status based on HEAD to index
* comparison, not looking at working directory changes.
* - GIT_STATUS_SHOW_WORKDIR_ONLY only gives status based on index to
* working directory comparison, not comparing the index to the HEAD.
* - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR runs index-only then workdir-only,
* issuing (up to) two callbacks per file (first index, then workdir).
* This is slightly more efficient than separate calls and can make it
* easier to emulate plain `git status` text output.
*/
typedef enum {
GIT_STATUS_SHOW_INDEX_AND_WORKDIR = 0,
GIT_STATUS_SHOW_INDEX_ONLY = 1,
GIT_STATUS_SHOW_WORKDIR_ONLY = 2,
GIT_STATUS_SHOW_INDEX_THEN_WORKDIR = 3,
} git_status_show_t;
/**
@ -108,7 +107,7 @@ typedef enum {
* - 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 tha rename
* - 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
@ -117,6 +116,11 @@ typedef enum {
* - 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
* - GIT_STATUS_OPT_NO_REFRESH bypasses the default status behavior of
* doing a "soft" index reload (i.e. reloading the index data if the
* file on disk has been modified outside libgit2).
*
* Calling `git_status_foreach()` is like calling the extended version
* with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED,
@ -135,6 +139,8 @@ typedef enum {
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_NO_REFRESH = (1u << 12),
} git_status_opt_t;
#define GIT_STATUS_OPT_DEFAULTS \
@ -235,12 +241,12 @@ GIT_EXTERN(int) git_status_foreach_ext(
* This is not quite the same as calling `git_status_foreach_ext()` with
* 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 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,
* index, and work tree, GIT_EINVALIDPATH if `path` points at a folder,
* GIT_EAMBIGUOUS if "path" matches multiple files, -1 on other error.
* index, and work tree, GIT_EAMBIGUOUS if `path` matches multiple files
* or if it refers to a folder, and -1 on other errors.
*/
GIT_EXTERN(int) git_status_file(
unsigned int *status_flags,

View File

@ -14,51 +14,18 @@
/**
* @file git2/submodule.h
* @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
* 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
* directory that look like submodules (i.e. a git repo) but are not
* 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
* `git_submodule_set_update()` and wish to reset to the original default.
* @defgroup git_submodule Git submodule management routines
* @ingroup Git
* @{
*/
typedef enum {
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;
GIT_BEGIN_DECL
/**
* Return codes for submodule status.
@ -119,19 +86,9 @@ typedef enum {
GIT_SUBMODULE_STATUS_WD_UNTRACKED = (1u << 13),
} git_submodule_status_t;
#define GIT_SUBMODULE_STATUS__IN_FLAGS \
(GIT_SUBMODULE_STATUS_IN_HEAD | \
GIT_SUBMODULE_STATUS_IN_INDEX | \
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__IN_FLAGS 0x000Fu
#define GIT_SUBMODULE_STATUS__INDEX_FLAGS 0x0070u
#define GIT_SUBMODULE_STATUS__WD_FLAGS 0x3F80u
#define GIT_SUBMODULE_STATUS_IS_UNMODIFIED(S) \
(((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);
/**
* 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
* 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.
* The working directory will be consider clean so long as there is a
* 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_submodule *submodule);
@ -382,15 +347,17 @@ GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore(
/**
* Set the ignore rule for the submodule.
*
* This sets the ignore rule in memory for the submodule. This will be used
* for any following actions (such as `git_submodule_status()`) while the
* submodule is in memory. You should call `git_submodule_save()` if you
* want to persist the new ignore role.
* This sets the in-memory ignore rule for the submodule which will
* control the behavior of `git_submodule_status()`.
*
* Calling this again with GIT_SUBMODULE_IGNORE_DEFAULT or calling
* `git_submodule_reload()` will revert the rule to the value that was in the
* original config.
* To make changes persistent, call `git_submodule_save()` to write the
* value to disk (in the ".gitmodules" and ".git/config" files).
*
* 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
*/
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);
/**
* 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_submodule *submodule);
@ -406,13 +382,17 @@ GIT_EXTERN(git_submodule_update_t) git_submodule_update(
/**
* Set the update rule for the submodule.
*
* This sets the update rule in memory for the submodule. You should call
* `git_submodule_save()` if you want to persist the new update rule.
* The initial value comes from the ".git/config" setting of
* `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
* `git_submodule_reload()` will revert the rule to the value that was in the
* original config.
* Calling this again with GIT_SUBMODULE_UPDATE_RESET or calling
* `git_submodule_reload()` will revert the rule to the on disk value.
*
* @param submodule The submodule to update
* @param update The new value to use
* @return old value for update
*/
GIT_EXTERN(git_submodule_update_t) git_submodule_set_update(

View File

@ -20,6 +20,33 @@
*/
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
* access a configuration file
@ -31,11 +58,11 @@ struct git_config_backend {
/* Open means open the file/database and parse if necessary */
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_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_multivar)(git_config_backend *cfg, const char *name, const char *regexp, const char *value);
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 (*del_multivar)(struct git_config_backend *, const char *key, const char *regexp);
int (*iterator)(git_config_iterator **, struct git_config_backend *);
int (*refresh)(struct git_config_backend *);
void (*free)(struct git_config_backend *);
};

292
include/git2/sys/filter.h Normal file
View File

@ -0,0 +1,292 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_sys_git_filter_h__
#define INCLUDE_sys_git_filter_h__
#include "git2/filter.h"
/**
* @file git2/sys/filter.h
* @brief Git filter backend and plugin routines
* @defgroup git_backend Git custom backend APIs
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
/**
* Look up a filter by name
*
* @param name The name of the filter
* @return Pointer to the filter object or NULL if not found
*/
GIT_EXTERN(git_filter *) git_filter_lookup(const char *name);
#define GIT_FILTER_CRLF "crlf"
#define GIT_FILTER_IDENT "ident"
/**
* This is priority that the internal CRLF filter will be registered with
*/
#define GIT_FILTER_CRLF_PRIORITY 0
/**
* This is priority that the internal ident filter will be registered with
*/
#define GIT_FILTER_IDENT_PRIORITY 100
/**
* This is priority to use with a custom filter to imitate a core Git
* filter driver, so that it will be run last on checkout and first on
* checkin. You do not have to use this, but it helps compatibility.
*/
#define GIT_FILTER_DRIVER_PRIORITY 200
/**
* Create a new empty filter list
*
* Normally you won't use this because `git_filter_list_load` will create
* the filter list for you, but you can use this in combination with the
* `git_filter_lookup` and `git_filter_list_push` functions to assemble
* your own chains of filters.
*/
GIT_EXTERN(int) git_filter_list_new(
git_filter_list **out, git_repository *repo, git_filter_mode_t mode);
/**
* Add a filter to a filter list with the given payload.
*
* Normally you won't have to do this because the filter list is created
* by calling the "check" function on registered filters when the filter
* attributes are set, but this does allow more direct manipulation of
* filter lists when desired.
*
* Note that normally the "check" function can set up a payload for the
* filter. Using this function, you can either pass in a payload if you
* know the expected payload format, or you can pass NULL. Some filters
* may fail with a NULL payload. Good luck!
*/
GIT_EXTERN(int) git_filter_list_push(
git_filter_list *fl, git_filter *filter, void *payload);
/**
* Look up how many filters are in the list
*
* We will attempt to apply all of these filters to any data passed in,
* but note that the filter apply action still has the option of skipping
* data that is passed in (for example, the CRLF filter will skip data
* that appears to be binary).
*
* @param fl A filter list
* @return The number of filters in the list
*/
GIT_EXTERN(size_t) git_filter_list_length(const git_filter_list *fl);
/**
* A filter source represents a file/blob to be processed
*/
typedef struct git_filter_source git_filter_source;
/**
* Get the repository that the source data is coming from.
*/
GIT_EXTERN(git_repository *) git_filter_source_repo(const git_filter_source *src);
/**
* Get the path that the source data is coming from.
*/
GIT_EXTERN(const char *) git_filter_source_path(const git_filter_source *src);
/**
* Get the file mode of the source file
* If the mode is unknown, this will return 0
*/
GIT_EXTERN(uint16_t) git_filter_source_filemode(const git_filter_source *src);
/**
* Get the OID of the source
* If the OID is unknown (often the case with GIT_FILTER_CLEAN) then
* this will return NULL.
*/
GIT_EXTERN(const git_oid *) git_filter_source_id(const git_filter_source *src);
/**
* Get the git_filter_mode_t to be applied
*/
GIT_EXTERN(git_filter_mode_t) git_filter_source_mode(const git_filter_source *src);
/*
* struct git_filter
*
* The filter lifecycle:
* - initialize - first use of filter
* - shutdown - filter removed/unregistered from system
* - check - considering filter for file
* - apply - apply filter to file contents
* - cleanup - done with file
*/
/**
* Initialize callback on filter
*
* Specified as `filter.initialize`, this is an optional callback invoked
* before a filter is first used. It will be called once at most.
*
* If non-NULL, the filter's `initialize` callback will be invoked right
* before the first use of the filter, so you can defer expensive
* initialization operations (in case libgit2 is being used in a way that
* doesn't need the filter).
*/
typedef int (*git_filter_init_fn)(git_filter *self);
/**
* Shutdown callback on filter
*
* Specified as `filter.shutdown`, this is an optional callback invoked
* when the filter is unregistered or when libgit2 is shutting down. It
* will be called once at most and should release resources as needed.
*
* Typically this function will free the `git_filter` object itself.
*/
typedef void (*git_filter_shutdown_fn)(git_filter *self);
/**
* Callback to decide if a given source needs this filter
*
* Specified as `filter.check`, this is an optional callback that checks
* if filtering is needed for a given source.
*
* It should return 0 if the filter should be applied (i.e. success),
* GIT_PASSTHROUGH if the filter should not be applied, or an error code
* to fail out of the filter processing pipeline and return to the caller.
*
* The `attr_values` will be set to the values of any attributes given in
* the filter definition. See `git_filter` below for more detail.
*
* The `payload` will be a pointer to a reference payload for the filter.
* This will start as NULL, but `check` can assign to this pointer for
* later use by the `apply` callback. Note that the value should be heap
* allocated (not stack), so that it doesn't go away before the `apply`
* callback can use it. If a filter allocates and assigns a value to the
* `payload`, it will need a `cleanup` callback to free the payload.
*/
typedef int (*git_filter_check_fn)(
git_filter *self,
void **payload, /* points to NULL ptr on entry, may be set */
const git_filter_source *src,
const char **attr_values);
/**
* Callback to actually perform the data filtering
*
* Specified as `filter.apply`, this is the callback that actually filters
* data. If it successfully writes the output, it should return 0. Like
* `check`, it can return GIT_PASSTHROUGH to indicate that the filter
* doesn't want to run. Other error codes will stop filter processing and
* return to the caller.
*
* The `payload` value will refer to any payload that was set by the
* `check` callback. It may be read from or written to as needed.
*/
typedef int (*git_filter_apply_fn)(
git_filter *self,
void **payload, /* may be read and/or set */
git_buf *to,
const git_buf *from,
const git_filter_source *src);
/**
* Callback to clean up after filtering has been applied
*
* Specified as `filter.cleanup`, this is an optional callback invoked
* after the filter has been applied. If the `check` or `apply` callbacks
* allocated a `payload` to keep per-source filter state, use this
* callback to free that payload and release resources as required.
*/
typedef void (*git_filter_cleanup_fn)(
git_filter *self,
void *payload);
/**
* Filter structure used to register custom filters.
*
* To associate extra data with a filter, allocate extra data and put the
* `git_filter` struct at the start of your data buffer, then cast the
* `self` pointer to your larger structure when your callback is invoked.
*
* `version` should be set to GIT_FILTER_VERSION
*
* `attributes` is a whitespace-separated list of attribute names to check
* for this filter (e.g. "eol crlf text"). If the attribute name is bare,
* it will be simply loaded and passed to the `check` callback. If it has
* a value (i.e. "name=value"), the attribute must match that value for
* the filter to be applied.
*
* The `initialize`, `shutdown`, `check`, `apply`, and `cleanup` callbacks
* are all documented above with the respective function pointer typedefs.
*/
struct git_filter {
unsigned int version;
const char *attributes;
git_filter_init_fn initialize;
git_filter_shutdown_fn shutdown;
git_filter_check_fn check;
git_filter_apply_fn apply;
git_filter_cleanup_fn cleanup;
};
#define GIT_FILTER_VERSION 1
/**
* Register a filter under a given name with a given priority.
*
* As mentioned elsewhere, the initialize callback will not be invoked
* immediately. It is deferred until the filter is used in some way.
*
* A filter's attribute checks and `check` and `apply` callbacks will be
* issued in order of `priority` on smudge (to workdir), and in reverse
* order of `priority` on clean (to odb).
*
* Two filters are preregistered with libgit2:
* - GIT_FILTER_CRLF with priority 0
* - GIT_FILTER_IDENT with priority 100
*
* Currently the filter registry is not thread safe, so any registering or
* deregistering of filters must be done outside of any possible usage of
* the filters (i.e. during application setup or shutdown).
*
* @param name A name by which the filter can be referenced. Attempting
* to register with an in-use name will return GIT_EEXISTS.
* @param filter The filter definition. This pointer will be stored as is
* by libgit2 so it must be a durable allocation (either static
* or on the heap).
* @param priority The priority for filter application
* @return 0 on successful registry, error code <0 on failure
*/
GIT_EXTERN(int) git_filter_register(
const char *name, git_filter *filter, int priority);
/**
* Remove the filter with the given name
*
* Attempting to remove the builtin libgit2 filters is not permitted and
* will return an error.
*
* Currently the filter registry is not thread safe, so any registering or
* deregistering of filters must be done outside of any possible usage of
* the filters (i.e. during application setup or shutdown).
*
* @param name The name under which the filter was registered
* @return 0 on success, error code <0 on failure
*/
GIT_EXTERN(int) git_filter_unregister(const char *name);
/** @} */
GIT_END_DECL
#endif

View File

@ -72,7 +72,6 @@ GIT_EXTERN(int) git_index_name_add(git_index *index,
* Remove all filename conflict entries.
*
* @param index an existing index object
* @return 0 or an error code
*/
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
*
* @param index an existing index object
* @return 0 or an error code
*/
GIT_EXTERN(void) git_index_reuc_clear(git_index *index);

View File

@ -48,12 +48,12 @@ struct git_odb_backend {
int (* read_header)(
size_t *, git_otype *, git_odb_backend *, const git_oid *);
/* The writer may assume that the object
* has already been hashed and is passed
* in the first parameter.
/**
* Write an object into the backend. The id of the object has
* already been calculated and is passed in.
*/
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)(
git_odb_stream **, git_odb_backend *, size_t, git_otype);
@ -64,13 +64,23 @@ struct git_odb_backend {
int (* exists)(
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 (* foreach)(
git_odb_backend *, git_odb_foreach_cb cb, void *payload);
int (* writepack)(
git_odb_writepack **, git_odb_backend *,
git_odb_writepack **, git_odb_backend *, git_odb *odb,
git_transfer_progress_callback progress_cb, void *progress_payload);
void (* free)(git_odb_backend *);

View File

@ -103,7 +103,7 @@ struct git_refdb_backend {
* Deletes the given reference from the refdb. A refdb implementation
* must provide this function.
*/
int (*delete)(git_refdb_backend *backend, const char *ref_name);
int (*del)(git_refdb_backend *backend, const char *ref_name);
/**
* Suggests that the given refdb compress or optimize its references.
@ -119,10 +119,30 @@ struct git_refdb_backend {
* provide this function; if it is not provided, nothing will be done.
*/
void (*free)(git_refdb_backend *backend);
/**
* Read the reflog for the given reference name.
*/
int (*reflog_read)(git_reflog **out, git_refdb_backend *backend, const char *name);
/**
* Write a reflog to disk.
*/
int (*reflog_write)(git_refdb_backend *backend, git_reflog *reflog);
/**
* Rename a reflog
*/
int (*reflog_rename)(git_refdb_backend *_backend, const char *old_name, const char *new_name);
/**
* Remove a reflog.
*/
int (*reflog_delete)(git_refdb_backend *backend, const char *name);
};
#define GIT_ODB_BACKEND_VERSION 1
#define GIT_ODB_BACKEND_INIT {GIT_ODB_BACKEND_VERSION}
#define GIT_REFDB_BACKEND_VERSION 1
#define GIT_REFDB_BACKEND_INIT {GIT_REFDB_BACKEND_VERSION}
/**
* Constructors for default filesystem-based refdb backend

21
include/git2/sys/reflog.h Normal file
View File

@ -0,0 +1,21 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_sys_git_reflog_h__
#define INCLUDE_sys_git_reflog_h__
#include "git2/common.h"
#include "git2/types.h"
#include "git2/oid.h"
GIT_BEGIN_DECL
GIT_EXTERN(git_reflog_entry *) git_reflog_entry__alloc(void);
GIT_EXTERN(void) git_reflog_entry__free(git_reflog_entry *entry);
GIT_END_DECL
#endif

View File

@ -16,7 +16,7 @@
*
* @param name the reference name
* @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
*/
GIT_EXTERN(git_reference *) git_reference__alloc(
@ -28,7 +28,7 @@ GIT_EXTERN(git_reference *) git_reference__alloc(
* Create a new symbolic reference.
*
* @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
*/
GIT_EXTERN(git_reference *) git_reference__alloc_symbolic(

View File

@ -27,7 +27,6 @@ GIT_BEGIN_DECL
*/
GIT_EXTERN(int) git_repository_new(git_repository **out);
/**
* Reset all the internal state in a repository.
*
@ -41,6 +40,25 @@ GIT_EXTERN(int) git_repository_new(git_repository **out);
*/
GIT_EXTERN(void) git_repository__cleanup(git_repository *repo);
/**
* Update the filesystem config settings for an open repository
*
* When a repository is initialized, config values are set based on the
* properties of the filesystem that the repository is on, such as
* "core.ignorecase", "core.filemode", "core.symlinks", etc. If the
* repository is moved to a new filesystem, these properties may no
* longer be correct and API calls may not behave as expected. This
* call reruns the phase of repository initialization that sets those
* properties to compensate for the current filesystem of the repo.
*
* @param repo A repository object
* @param recurse_submodules Should submodules be updated recursively
* @returrn 0 on success, < 0 on error
*/
GIT_EXTERN(int) git_repository_reinit_filesystem(
git_repository *repo,
int recurse_submodules);
/**
* Set the configuration file for this repository
*

View File

@ -28,22 +28,31 @@ GIT_BEGIN_DECL
*** Begin interface for credentials acquisition ***
*/
/** Authentication type requested */
typedef enum {
/* git_cred_userpass_plaintext */
GIT_CREDTYPE_USERPASS_PLAINTEXT = 1,
GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE = 2,
GIT_CREDTYPE_SSH_PUBLICKEY = 3,
GIT_CREDTYPE_USERPASS_PLAINTEXT = (1u << 0),
/* git_cred_ssh_key */
GIT_CREDTYPE_SSH_KEY = (1u << 1),
/* git_cred_ssh_custom */
GIT_CREDTYPE_SSH_CUSTOM = (1u << 2),
/* git_cred_default */
GIT_CREDTYPE_DEFAULT = (1u << 3),
} git_credtype_t;
/* The base structure for all credential types */
typedef struct git_cred {
git_credtype_t credtype;
void (*free)(
struct git_cred *cred);
} git_cred;
typedef struct git_cred git_cred;
/* A plaintext username and password */
typedef struct git_cred_userpass_plaintext {
struct git_cred {
git_credtype_t credtype;
void (*free)(git_cred *cred);
};
/** A plaintext username and password */
typedef struct {
git_cred parent;
char *username;
char *password;
@ -51,27 +60,46 @@ typedef struct git_cred_userpass_plaintext {
#ifdef GIT_SSH
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 */
typedef struct git_cred_ssh_keyfile_passphrase {
/**
* A ssh key from disk
*/
typedef struct git_cred_ssh_key {
git_cred parent;
char *username;
char *publickey;
char *privatekey;
char *passphrase;
} git_cred_ssh_keyfile_passphrase;
} git_cred_ssh_key;
/* A ssh public key and authentication callback */
typedef struct git_cred_ssh_publickey {
/**
* A key with a custom signature function
*/
typedef struct git_cred_ssh_custom {
git_cred parent;
char *username;
char *publickey;
size_t publickey_len;
void *sign_callback;
void *sign_data;
} git_cred_ssh_publickey;
#endif
} git_cred_ssh_custom;
/** A key for NTLM/Kerberos "default" credentials */
typedef struct git_cred git_cred_default;
/**
* Creates a new plain-text username and password credential object.
* 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);
/**
* Create a new plain-text username and password credential object.
* The supplied credential parameter will be internally duplicated.
*
* @param out The newly created credential object.
@ -84,52 +112,68 @@ GIT_EXTERN(int) git_cred_userpass_plaintext_new(
const char *username,
const char *password);
#ifdef GIT_SSH
/**
* Creates a new ssh key file and passphrase credential object.
* Create a new passphrase-protected ssh key credential object.
* The supplied credential parameter will be internally duplicated.
*
* @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 privatekey The path to the private key of the credential.
* @param passphrase The passphrase of the credential.
* @return 0 for success or an error code for failure
*/
GIT_EXTERN(int) git_cred_ssh_keyfile_passphrase_new(
GIT_EXTERN(int) git_cred_ssh_key_new(
git_cred **out,
const char *username,
const char *publickey,
const char *privatekey,
const char *passphrase);
/**
* Creates a new ssh public key credential object.
* Create an ssh key credential with a custom signing function.
*
* This lets you use your own function to sign the challenge.
*
* This function and its credential type is provided for completeness
* and wraps `libssh2_userauth_publickey()`, which is undocumented.
*
* The supplied credential parameter will be internally duplicated.
*
* @param out The newly created credential object.
* @param username username to use to authenticate
* @param publickey The bytes of the public key.
* @param publickey_len The length of the public key in bytes.
* @param sign_callback The callback method for authenticating.
* @param sign_data The abstract data sent to the sign_callback method.
* @param sign_fn The callback method to sign the data during the challenge.
* @param sign_data The data to pass to the sign function.
* @return 0 for success or an error code for failure
*/
GIT_EXTERN(int) git_cred_ssh_publickey_new(
GIT_EXTERN(int) git_cred_ssh_custom_new(
git_cred **out,
const char *username,
const char *publickey,
size_t publickey_len,
git_cred_sign_callback,
git_cred_sign_callback sign_fn,
void *sign_data);
#endif
/**
* Create a "default" credential usable for Negotiate mechanisms like NTLM
* or Kerberos authentication.
*
* @return 0 for success or an error code for failure
*/
GIT_EXTERN(int) git_cred_default_new(git_cred **out);
/**
* Signature of a function which acquires a credential object.
*
* @param cred The newly created credential object.
* @param url The resource for which we are demanding a credential.
* @param username_from_url The username that was embedded in a "user@host"
* - cred: The newly created credential object.
* - url: The resource for which we are demanding a credential.
* - username_from_url: The username that was embedded in a "user@host"
* remote url, or NULL if not included.
* @param allowed_types A bitmask stating which cred types are OK to return.
* @param payload The payload provided when specifying this callback.
* @return 0 for success or an error code for failure
* - allowed_types: A bitmask stating which cred types are OK to return.
* - payload: The payload provided when specifying this callback.
* - returns 0 for success or non-zero to indicate an error
*/
typedef int (*git_cred_acquire_cb)(
git_cred **cred,
@ -150,39 +194,45 @@ typedef enum {
GIT_TRANSPORTFLAGS_NO_CHECK_CERT = 1
} git_transport_flags_t;
typedef void (*git_transport_message_cb)(const char *str, int len, void *data);
typedef int (*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;
/* 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 error_cb,
void *payload);
/* Connect the transport to the remote repository, using the given
* direction. */
int (*connect)(struct git_transport *transport,
int (*connect)(
git_transport *transport,
const char *url,
git_cred_acquire_cb cred_acquire_cb,
void *cred_acquire_payload,
int direction,
int flags);
/* This function may be called after a successful call to connect(). The
* provided callback is invoked for each ref discovered on the remote
* end. */
int (*ls)(struct git_transport *transport,
git_headlist_cb list_cb,
void *payload);
/* This function may be called after a successful call to
* connect(). The array returned is owned by the transport and
* is guranteed until the next call of a transport function. */
int (*ls)(
const git_remote_head ***out,
size_t *size,
git_transport *transport);
/* 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
* the direction is FETCH. The function performs a negotiation to calculate
* the wants list for the fetch. */
int (*negotiate_fetch)(struct git_transport *transport,
int (*negotiate_fetch)(
git_transport *transport,
git_repository *repo,
const git_remote_head * const *refs,
size_t count);
@ -190,28 +240,29 @@ typedef struct git_transport {
/* This function may be called after a successful call to negotiate_fetch(),
* when the direction is FETCH. This function retrieves the pack file for
* the fetch from the remote end. */
int (*download_pack)(struct git_transport *transport,
int (*download_pack)(
git_transport *transport,
git_repository *repo,
git_transfer_progress *stats,
git_transfer_progress_callback progress_cb,
void *progress_payload);
/* 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() */
int (*read_flags)(struct git_transport *transport, int *flags);
int (*read_flags)(git_transport *transport, int *flags);
/* 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
* connection to the remote end. */
int (*close)(struct git_transport *transport);
int (*close)(git_transport *transport);
/* Frees/destructs the git_transport object. */
void (*free)(struct git_transport *transport);
} git_transport;
void (*free)(git_transport *transport);
};
#define GIT_TRANSPORT_VERSION 1
#define GIT_TRANSPORT_INIT {GIT_TRANSPORT_VERSION}
@ -231,6 +282,40 @@ GIT_EXTERN(int) git_transport_new(git_transport **out, git_remote *owner, const
/* Signature of a function which creates a transport */
typedef int (*git_transport_cb)(git_transport **out, git_remote *owner, void *param);
/**
* Add a custom transport definition, to be used in addition to the built-in
* set of transports that come with libgit2.
*
* The caller is responsible for synchronizing calls to git_transport_register
* and git_transport_unregister with other calls to the library that
* instantiate transports.
*
* @param prefix The scheme (ending in "://") to match, i.e. "git://"
* @param priority The priority of this transport relative to others with
* the same prefix. Built-in transports have a priority of 1.
* @param cb The callback used to create an instance of the transport
* @param param A fixed parameter to pass to cb at creation time
* @return 0 or an error code
*/
GIT_EXTERN(int) git_transport_register(
const char *prefix,
unsigned priority,
git_transport_cb cb,
void *param);
/**
*
* Unregister a custom transport definition which was previously registered
* with git_transport_register.
*
* @param prefix From the previous call to git_transport_register
* @param priority From the previous call to git_transport_register
* @return 0 or an error code
*/
GIT_EXTERN(int) git_transport_unregister(
const char *prefix,
unsigned priority);
/* Transports which come with libgit2 (match git_transport_cb). The expected
* value for "param" is listed in-line below. */
@ -299,35 +384,36 @@ typedef enum {
GIT_SERVICE_RECEIVEPACK = 4,
} 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
* from a subtransport */
typedef struct git_smart_subtransport_stream {
struct git_smart_subtransport_stream {
/* The owning subtransport */
struct git_smart_subtransport *subtransport;
git_smart_subtransport *subtransport;
int (*read)(
struct git_smart_subtransport_stream *stream,
git_smart_subtransport_stream *stream,
char *buffer,
size_t buf_size,
size_t *bytes_read);
int (*write)(
struct git_smart_subtransport_stream *stream,
git_smart_subtransport_stream *stream,
const char *buffer,
size_t len);
void (*free)(
struct git_smart_subtransport_stream *stream);
} git_smart_subtransport_stream;
git_smart_subtransport_stream *stream);
};
/* An implementation of a subtransport which carries data for the
* smart transport */
typedef struct git_smart_subtransport {
struct git_smart_subtransport {
int (* action)(
git_smart_subtransport_stream **out,
struct git_smart_subtransport *transport,
git_smart_subtransport *transport,
const char *url,
git_smart_service_t action);
@ -337,10 +423,10 @@ typedef struct git_smart_subtransport {
*
* 1. UPLOADPACK_LS -> UPLOADPACK
* 2. RECEIVEPACK_LS -> RECEIVEPACK */
int (* close)(struct git_smart_subtransport *transport);
int (*close)(git_smart_subtransport *transport);
void (* free)(struct git_smart_subtransport *transport);
} git_smart_subtransport;
void (*free)(git_smart_subtransport *transport);
};
/* A function which creates a new subtransport for the smart transport */
typedef int (*git_smart_subtransport_cb)(

View File

@ -198,6 +198,17 @@ GIT_EXTERN(git_otype) git_tree_entry_type(const git_tree_entry *entry);
*/
GIT_EXTERN(git_filemode_t) git_tree_entry_filemode(const git_tree_entry *entry);
/**
* Get the raw UNIX file attributes of a tree entry
*
* This function does not perform any normalization and is only useful
* if you need to be able to recreate the original tree object.
*
* @param entry a tree entry
* @return filemode as an integer
*/
GIT_EXTERN(git_filemode_t) git_tree_entry_filemode_raw(const git_tree_entry *entry);
/**
* Compare two tree entries
*
@ -208,7 +219,7 @@ 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);
/**
* 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.
*

View File

@ -174,6 +174,9 @@ typedef struct git_reference_iterator git_reference_iterator;
/** Merge heads, the input to merge */
typedef struct git_merge_head git_merge_head;
/** Merge result */
typedef struct git_merge_result git_merge_result;
/** Representation of a status collection */
typedef struct git_status_list git_status_list;
@ -212,11 +215,21 @@ typedef struct git_remote_callbacks git_remote_callbacks;
/**
* This is passed as the first argument to the callback to allow the
* user to see the progress.
*
* - total_objects: number of objects in the packfile being downloaded
* - indexed_objects: received objects that have been hashed
* - received_objects: objects which have been downloaded
* - local_objects: locally-available objects that have been injected
* in order to fix a thin pack.
* - received-bytes: size of the packfile received up to now
*/
typedef struct git_transfer_progress {
unsigned int total_objects;
unsigned int indexed_objects;
unsigned int received_objects;
unsigned int local_objects;
unsigned int total_deltas;
unsigned int indexed_deltas;
size_t received_bytes;
} git_transfer_progress;
@ -229,6 +242,87 @@ typedef struct git_transfer_progress {
*/
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.
* - GIT_SUBMODULE_UPDATE_DEFAULT: not used except as static initializer
* when we don't want any particular update rule to be specified.
*/
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_DEFAULT = 0
} 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
* - GIT_SUBMODULE_IGNORE_DEFAULT: not used except as static initializer
* when we don't want any particular ignore rule to be specified.
*/
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_DEFAULT = 0
} git_submodule_ignore_t;
/** @} */
GIT_END_DECL

View File

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

View File

@ -4,6 +4,7 @@ includedir=@CMAKE_INSTALL_PREFIX@/@INCLUDE_INSTALL_DIR@
Name: libgit2
Description: The git library, take 2
Version: @LIBGIT2_VERSION_STRING@
Requires: libcrypto
Libs: -L${libdir} -lgit2 -lz -lcrypto
Requires.private: @LIBGIT2_PC_REQUIRES@
Libs.private: @LIBGIT2_PC_LIBS@
Libs: -L${libdir} -lgit2
Cflags: -I${includedir}

View File

@ -1,6 +0,0 @@
To build RPM pakcages for Fedora, follow these steps:
cp packaging/rpm/libgit2.spec ~/rpmbuild/SPECS
cd ~/rpmbuild/SOURCES
wget https://github.com/downloads/libgit2/libgit2/libgit2-0.16.0.tar.gz
cd ~/rpmbuild/SPECS
rpmbuild -ba libgit2.spec

View File

@ -1,106 +0,0 @@
#
# spec file for package libgit2
#
# Copyright (c) 2012 Saleem Ansari <tuxdna@gmail.com>
# Copyright (c) 2012 SUSE LINUX Products GmbH, Nuernberg, Germany.
# Copyright (c) 2011, Sascha Peilicke <saschpe@gmx.de>
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
# Please submit bugfixes or comments via http://bugs.opensuse.org/
#
Name: libgit2
Version: 0.16.0
Release: 1
Summary: C git library
License: GPL-2.0 with linking
Group: Development/Libraries/C and C++
Url: http://libgit2.github.com/
Source0: https://github.com/downloads/libgit2/libgit2/libgit2-0.16.0.tar.gz
BuildRequires: cmake
BuildRequires: pkgconfig
BuildRoot: %{_tmppath}/%{name}-%{version}-build
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos_version}
BuildRequires: openssl-devel
%else
BuildRequires: libopenssl-devel
%endif
%description
libgit2 is a portable, pure C implementation of the Git core methods
provided as a re-entrant linkable library with a solid API, allowing
you to write native speed custom Git applications in any language
with bindings.
%package -n %{name}-0
Summary: C git library
Group: System/Libraries
%description -n %{name}-0
libgit2 is a portable, pure C implementation of the Git core methods
provided as a re-entrant linkable library with a solid API, allowing
you to write native speed custom Git applications in any language
with bindings.
%package devel
Summary: C git library
Group: Development/Libraries/C and C++
Requires: %{name}-0 >= %{version}
%description devel
This package contains all necessary include files and libraries needed
to compile and develop applications that use libgit2.
%prep
%setup -q
%build
cmake . \
-DCMAKE_C_FLAGS:STRING="%{optflags}" \
-DCMAKE_INSTALL_PREFIX:PATH=%{_prefix} \
-DLIB_INSTALL_DIR:PATH=%{_libdir}S
make %{?_smp_mflags}
%install
%make_install
%post -n %{name}-0 -p /sbin/ldconfig
%postun -n %{name}-0 -p /sbin/ldconfig
%files -n %{name}-0
%defattr (-,root,root)
%doc AUTHORS COPYING README.md
%{_libdir}/%{name}.so.*
%files devel
%defattr (-,root,root)
%doc CONVENTIONS examples
%{_libdir}/%{name}.so
%{_includedir}/git2*
%{_libdir}/pkgconfig/libgit2.pc
%changelog
* Tue Mar 04 2012 tuxdna@gmail.com
- Update to version 0.16.0
* Tue Jan 31 2012 jengelh@medozas.de
- Provide pkgconfig symbols
* Thu Oct 27 2011 saschpe@suse.de
- Change license to 'GPL-2.0 with linking', fixes bnc#726789
* Wed Oct 26 2011 saschpe@suse.de
- Update to version 0.15.0:
* Upstream doesn't provide changes
- Removed outdated %%clean section
* Tue Jan 18 2011 saschpe@gmx.de
- Proper Requires for devel package
* Tue Jan 18 2011 saschpe@gmx.de
- Set BuildRequires to "openssl-devel" also for RHEL and CentOS
* Tue Jan 18 2011 saschpe@gmx.de
- Initial commit (0.0.1)
- Added patch to fix shared library soname

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) \
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) \
do { git__free((a).ptr); git_array_init(a); } while (0)
#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 */
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;
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) {
git_array_clear(*a);
return NULL;
} else {
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) \
((a).size >= (a).asize) ? \
git_array_grow((git_array_generic_t *)&(a), sizeof(*(a).ptr)) : \
(a).ptr ? &(a).ptr[(a).size++] : NULL
(((a).size >= (a).asize) ? \
git_array_grow(&(a), sizeof(*(a).ptr)) : \
((a).ptr ? &(a).ptr[(a).size++] : 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_size(a) (a).size
#define git_array_valid_index(a, i) ((i) < (a).size)
#endif

View File

@ -1,3 +1,4 @@
#include "common.h"
#include "repository.h"
#include "fileops.h"
#include "config.h"
@ -26,7 +27,6 @@ git_attr_t git_attr_value(const char *attr)
return GIT_ATTR_VALUE_T;
}
static int collect_attr_files(
git_repository *repo,
uint32_t flags,
@ -103,8 +103,6 @@ int git_attr_get_many(
attr_get_many_info *info = NULL;
size_t num_found = 0;
memset((void *)values, 0, sizeof(const char *) * num_attr);
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
return -1;
@ -141,6 +139,11 @@ int git_attr_get_many(
}
}
for (k = 0; k < num_attr; k++) {
if (!info[k].found)
values[k] = NULL;
}
cleanup:
git_vector_free(&files);
git_attr_path__free(&path);

View File

@ -39,7 +39,7 @@ int git_attr_file__new(
attrs->key = git_pool_malloc(attrs->pool, (uint32_t)len + 3);
GITERR_CHECK_ALLOC(attrs->key);
attrs->key[0] = '0' + from;
attrs->key[0] = '0' + (char)from;
attrs->key[1] = '#';
memcpy(&attrs->key[2], path, len);
attrs->key[len + 2] = '\0';
@ -79,10 +79,14 @@ int git_attr_file__parse_buffer(
while (!error && *scan) {
/* 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;
break;
}
rule->match.flags = GIT_ATTR_FNMATCH_ALLOWNEG |
GIT_ATTR_FNMATCH_ALLOWMACRO;
}
/* parse the next "pattern attr attr attr" line */
if (!(error = git_attr_fnmatch__parse(
@ -351,8 +355,8 @@ int git_attr_fnmatch__parse(
if (parse_optimized_patterns(spec, pool, *base))
return 0;
spec->flags = (spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE);
allow_space = (spec->flags != 0);
spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING);
allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0);
pattern = *base;
@ -362,7 +366,7 @@ int git_attr_fnmatch__parse(
return GIT_ENOTFOUND;
}
if (*pattern == '[') {
if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) {
if (strncmp(pattern, "[attr]", 6) == 0) {
spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
pattern += 6;
@ -370,7 +374,7 @@ int git_attr_fnmatch__parse(
/* 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;
pattern++;
}

View File

@ -28,6 +28,12 @@
#define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6)
#define GIT_ATTR_FNMATCH_ICASE (1U << 7)
#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__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

476
src/blame.c Normal file
View File

@ -0,0 +1,476 @@
/*
* 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 "blame.h"
#include "git2/commit.h"
#include "git2/revparse.h"
#include "git2/revwalk.h"
#include "git2/tree.h"
#include "git2/diff.h"
#include "git2/blob.h"
#include "git2/signature.h"
#include "util.h"
#include "repository.h"
#include "blame_git.h"
static int hunk_byfinalline_search_cmp(const void *key, const void *entry)
{
uint16_t lineno = (uint16_t)*(size_t*)key;
git_blame_hunk *hunk = (git_blame_hunk*)entry;
if (lineno < hunk->final_start_line_number)
return -1;
if (lineno >= hunk->final_start_line_number + hunk->lines_in_hunk)
return 1;
return 0;
}
static int paths_cmp(const void *a, const void *b) { return git__strcmp((char*)a, (char*)b); }
static int hunk_cmp(const void *_a, const void *_b)
{
git_blame_hunk *a = (git_blame_hunk*)_a,
*b = (git_blame_hunk*)_b;
return a->final_start_line_number - b->final_start_line_number;
}
static bool hunk_ends_at_or_before_line(git_blame_hunk *hunk, size_t line)
{
return line >= (size_t)(hunk->final_start_line_number + hunk->lines_in_hunk - 1);
}
static bool hunk_starts_at_or_after_line(git_blame_hunk *hunk, size_t line)
{
return line <= hunk->final_start_line_number;
}
static git_blame_hunk* new_hunk(
uint16_t start,
uint16_t lines,
uint16_t orig_start,
const char *path)
{
git_blame_hunk *hunk = git__calloc(1, sizeof(git_blame_hunk));
if (!hunk) return NULL;
hunk->lines_in_hunk = lines;
hunk->final_start_line_number = start;
hunk->orig_start_line_number = orig_start;
hunk->orig_path = path ? git__strdup(path) : NULL;
return hunk;
}
static git_blame_hunk* dup_hunk(git_blame_hunk *hunk)
{
git_blame_hunk *newhunk = new_hunk(
hunk->final_start_line_number,
hunk->lines_in_hunk,
hunk->orig_start_line_number,
hunk->orig_path);
git_oid_cpy(&newhunk->orig_commit_id, &hunk->orig_commit_id);
git_oid_cpy(&newhunk->final_commit_id, &hunk->final_commit_id);
newhunk->boundary = hunk->boundary;
newhunk->final_signature = git_signature_dup(hunk->final_signature);
newhunk->orig_signature = git_signature_dup(hunk->orig_signature);
return newhunk;
}
static void free_hunk(git_blame_hunk *hunk)
{
git__free((void*)hunk->orig_path);
git_signature_free(hunk->final_signature);
git_signature_free(hunk->orig_signature);
git__free(hunk);
}
/* Starting with the hunk that includes start_line, shift all following hunks'
* final_start_line by shift_by lines */
static void shift_hunks_by(git_vector *v, size_t start_line, int shift_by)
{
size_t i;
if (!git_vector_bsearch2( &i, v, hunk_byfinalline_search_cmp, &start_line)) {
for (; i < v->length; i++) {
git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i];
hunk->final_start_line_number += shift_by;
}
}
}
git_blame* git_blame__alloc(
git_repository *repo,
git_blame_options opts,
const char *path)
{
git_blame *gbr = (git_blame*)git__calloc(1, sizeof(git_blame));
if (!gbr) {
giterr_set_oom();
return NULL;
}
git_vector_init(&gbr->hunks, 8, hunk_cmp);
git_vector_init(&gbr->paths, 8, paths_cmp);
gbr->repository = repo;
gbr->options = opts;
gbr->path = git__strdup(path);
git_vector_insert(&gbr->paths, git__strdup(path));
return gbr;
}
void git_blame_free(git_blame *blame)
{
size_t i;
git_blame_hunk *hunk;
char *path;
if (!blame) return;
git_vector_foreach(&blame->hunks, i, hunk)
free_hunk(hunk);
git_vector_free(&blame->hunks);
git_vector_foreach(&blame->paths, i, path)
git__free(path);
git_vector_free(&blame->paths);
git_array_clear(blame->line_index);
git__free((void*)blame->path);
git_blob_free(blame->final_blob);
git__free(blame);
}
uint32_t git_blame_get_hunk_count(git_blame *blame)
{
assert(blame);
return (uint32_t)blame->hunks.length;
}
const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t index)
{
assert(blame);
return (git_blame_hunk*)git_vector_get(&blame->hunks, index);
}
const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, uint32_t lineno)
{
size_t i;
assert(blame);
if (!git_vector_bsearch2( &i, &blame->hunks, hunk_byfinalline_search_cmp, &lineno)) {
return git_blame_get_hunk_byindex(blame, (uint32_t)i);
}
return NULL;
}
static void normalize_options(
git_blame_options *out,
const git_blame_options *in,
git_repository *repo)
{
git_blame_options dummy = GIT_BLAME_OPTIONS_INIT;
if (!in) in = &dummy;
memcpy(out, in, sizeof(git_blame_options));
/* No newest_commit => HEAD */
if (git_oid_iszero(&out->newest_commit)) {
git_reference_name_to_id(&out->newest_commit, repo, "HEAD");
}
/* min_line 0 really means 1 */
if (!out->min_line) out->min_line = 1;
/* max_line 0 really means N, but we don't know N yet */
/* Fix up option implications */
if (out->flags & GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES)
out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES)
out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES)
out->flags |= GIT_BLAME_TRACK_COPIES_SAME_FILE;
}
static git_blame_hunk *split_hunk_in_vector(
git_vector *vec,
git_blame_hunk *hunk,
size_t rel_line,
bool return_new)
{
size_t new_line_count;
git_blame_hunk *nh;
/* Don't split if already at a boundary */
if (rel_line <= 0 ||
rel_line >= hunk->lines_in_hunk)
{
return hunk;
}
new_line_count = hunk->lines_in_hunk - rel_line;
nh = new_hunk((uint16_t)(hunk->final_start_line_number+rel_line), (uint16_t)new_line_count,
(uint16_t)(hunk->orig_start_line_number+rel_line), hunk->orig_path);
git_oid_cpy(&nh->final_commit_id, &hunk->final_commit_id);
git_oid_cpy(&nh->orig_commit_id, &hunk->orig_commit_id);
/* Adjust hunk that was split */
hunk->lines_in_hunk -= (uint16_t)new_line_count;
git_vector_insert_sorted(vec, nh, NULL);
{
git_blame_hunk *ret = return_new ? nh : hunk;
return ret;
}
}
/*
* Construct a list of char indices for where lines begin
* Adapted from core git:
* https://github.com/gitster/git/blob/be5c9fb9049ed470e7005f159bb923a5f4de1309/builtin/blame.c#L1760-L1789
*/
static int index_blob_lines(git_blame *blame)
{
const char *buf = blame->final_buf;
git_off_t len = blame->final_buf_size;
int num = 0, incomplete = 0, bol = 1;
size_t *i;
if (len && buf[len-1] != '\n')
incomplete++; /* incomplete line at the end */
while (len--) {
if (bol) {
i = git_array_alloc(blame->line_index);
GITERR_CHECK_ALLOC(i);
*i = buf - blame->final_buf;
bol = 0;
}
if (*buf++ == '\n') {
num++;
bol = 1;
}
}
i = git_array_alloc(blame->line_index);
GITERR_CHECK_ALLOC(i);
*i = buf - blame->final_buf;
blame->num_lines = num + incomplete;
return blame->num_lines;
}
static git_blame_hunk* hunk_from_entry(git_blame__entry *e)
{
git_blame_hunk *h = new_hunk(
e->lno+1, e->num_lines, e->s_lno+1, e->suspect->path);
git_oid_cpy(&h->final_commit_id, git_commit_id(e->suspect->commit));
h->final_signature = git_signature_dup(git_commit_author(e->suspect->commit));
h->boundary = e->is_boundary ? 1 : 0;
return h;
}
static int load_blob(git_blame *blame)
{
int error;
if (blame->final_blob) return 0;
error = git_commit_lookup(&blame->final, blame->repository, &blame->options.newest_commit);
if (error < 0)
goto cleanup;
error = git_object_lookup_bypath((git_object**)&blame->final_blob,
(git_object*)blame->final, blame->path, GIT_OBJ_BLOB);
if (error < 0)
goto cleanup;
cleanup:
return error;
}
static int blame_internal(git_blame *blame)
{
int error;
git_blame__entry *ent = NULL;
git_blame__origin *o;
if ((error = load_blob(blame)) < 0 ||
(error = git_blame__get_origin(&o, blame, blame->final, blame->path)) < 0)
goto cleanup;
blame->final_buf = git_blob_rawcontent(blame->final_blob);
blame->final_buf_size = git_blob_rawsize(blame->final_blob);
ent = git__calloc(1, sizeof(git_blame__entry));
ent->num_lines = index_blob_lines(blame);
ent->lno = blame->options.min_line - 1;
ent->num_lines = ent->num_lines - blame->options.min_line + 1;
if (blame->options.max_line > 0)
ent->num_lines = blame->options.max_line - blame->options.min_line + 1;
ent->s_lno = ent->lno;
ent->suspect = o;
blame->ent = ent;
blame->path = blame->path;
git_blame__like_git(blame, blame->options.flags);
cleanup:
for (ent = blame->ent; ent; ) {
git_blame__entry *e = ent->next;
git_vector_insert(&blame->hunks, hunk_from_entry(ent));
git_blame__free_entry(ent);
ent = e;
}
return error;
}
/*******************************************************************************
* File blaming
******************************************************************************/
int git_blame_file(
git_blame **out,
git_repository *repo,
const char *path,
git_blame_options *options)
{
int error = -1;
git_blame_options normOptions = GIT_BLAME_OPTIONS_INIT;
git_blame *blame = NULL;
assert(out && repo && path);
normalize_options(&normOptions, options, repo);
blame = git_blame__alloc(repo, normOptions, path);
GITERR_CHECK_ALLOC(blame);
if ((error = load_blob(blame)) < 0)
goto on_error;
if ((error = blame_internal(blame)) < 0)
goto on_error;
*out = blame;
return 0;
on_error:
git_blame_free(blame);
return error;
}
/*******************************************************************************
* Buffer blaming
*******************************************************************************/
static bool hunk_is_bufferblame(git_blame_hunk *hunk)
{
return git_oid_iszero(&hunk->final_commit_id);
}
static int buffer_hunk_cb(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
void *payload)
{
git_blame *blame = (git_blame*)payload;
uint32_t wedge_line;
GIT_UNUSED(delta);
wedge_line = (hunk->old_lines == 0) ? hunk->new_start : hunk->old_start;
blame->current_diff_line = wedge_line;
blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byline(blame, wedge_line);
if (!blame->current_hunk) {
/* Line added at the end of the file */
blame->current_hunk = new_hunk(wedge_line, 0, wedge_line, blame->path);
git_vector_insert(&blame->hunks, blame->current_hunk);
} else if (!hunk_starts_at_or_after_line(blame->current_hunk, wedge_line)){
/* If this hunk doesn't start between existing hunks, split a hunk up so it does */
blame->current_hunk = split_hunk_in_vector(&blame->hunks, blame->current_hunk,
wedge_line - blame->current_hunk->orig_start_line_number, true);
}
return 0;
}
static int ptrs_equal_cmp(const void *a, const void *b) { return a<b ? -1 : a>b ? 1 : 0; }
static int buffer_line_cb(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
const git_diff_line *line,
void *payload)
{
git_blame *blame = (git_blame*)payload;
GIT_UNUSED(delta);
GIT_UNUSED(hunk);
GIT_UNUSED(line);
if (line->origin == GIT_DIFF_LINE_ADDITION) {
if (hunk_is_bufferblame(blame->current_hunk) &&
hunk_ends_at_or_before_line(blame->current_hunk, blame->current_diff_line)) {
/* Append to the current buffer-blame hunk */
blame->current_hunk->lines_in_hunk++;
shift_hunks_by(&blame->hunks, blame->current_diff_line+1, 1);
} else {
/* Create a new buffer-blame hunk with this line */
shift_hunks_by(&blame->hunks, blame->current_diff_line, 1);
blame->current_hunk = new_hunk((uint16_t)blame->current_diff_line, 1, 0, blame->path);
git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL);
}
blame->current_diff_line++;
}
if (line->origin == GIT_DIFF_LINE_DELETION) {
/* Trim the line from the current hunk; remove it if it's now empty */
size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk+1;
if (--(blame->current_hunk->lines_in_hunk) == 0) {
size_t i;
shift_base--;
if (!git_vector_search2(&i, &blame->hunks, ptrs_equal_cmp, blame->current_hunk)) {
git_vector_remove(&blame->hunks, i);
free_hunk(blame->current_hunk);
blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, (uint32_t)i);
}
}
shift_hunks_by(&blame->hunks, shift_base, -1);
}
return 0;
}
int git_blame_buffer(
git_blame **out,
git_blame *reference,
const char *buffer,
uint32_t buffer_len)
{
git_blame *blame;
git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
size_t i;
git_blame_hunk *hunk;
diffopts.context_lines = 0;
assert(out && reference && buffer && buffer_len);
blame = git_blame__alloc(reference->repository, reference->options, reference->path);
/* Duplicate all of the hunk structures in the reference blame */
git_vector_foreach(&reference->hunks, i, hunk) {
git_vector_insert(&blame->hunks, dup_hunk(hunk));
}
/* Diff to the reference blob */
git_diff_blob_to_buffer(reference->final_blob, blame->path,
buffer, buffer_len, blame->path,
&diffopts, NULL, buffer_hunk_cb, buffer_line_cb, blame);
*out = blame;
return 0;
}

93
src/blame.h Normal file
View File

@ -0,0 +1,93 @@
#ifndef INCLUDE_blame_h__
#define INCLUDE_blame_h__
#include "git2/blame.h"
#include "common.h"
#include "vector.h"
#include "diff.h"
#include "array.h"
#include "git2/oid.h"
/*
* One blob in a commit that is being suspected
*/
typedef struct git_blame__origin {
int refcnt;
struct git_blame__origin *previous;
git_commit *commit;
git_blob *blob;
char path[GIT_FLEX_ARRAY];
} git_blame__origin;
/*
* Each group of lines is described by a git_blame__entry; it can be split
* as we pass blame to the parents. They form a linked list in the
* scoreboard structure, sorted by the target line number.
*/
typedef struct git_blame__entry {
struct git_blame__entry *prev;
struct git_blame__entry *next;
/* the first line of this group in the final image;
* internally all line numbers are 0 based.
*/
int lno;
/* how many lines this group has */
int num_lines;
/* the commit that introduced this group into the final image */
git_blame__origin *suspect;
/* true if the suspect is truly guilty; false while we have not
* checked if the group came from one of its parents.
*/
bool guilty;
/* true if the entry has been scanned for copies in the current parent
*/
bool scanned;
/* the line number of the first line of this group in the
* suspect's file; internally all line numbers are 0 based.
*/
int s_lno;
/* how significant this entry is -- cached to avoid
* scanning the lines over and over.
*/
unsigned score;
/* Whether this entry has been tracked to a boundary commit.
*/
bool is_boundary;
} git_blame__entry;
struct git_blame {
const char *path;
git_repository *repository;
git_blame_options options;
git_vector hunks;
git_vector paths;
git_blob *final_blob;
git_array_t(size_t) line_index;
size_t current_diff_line;
git_blame_hunk *current_hunk;
/* Scoreboard fields */
git_commit *final;
git_blame__entry *ent;
int num_lines;
const char *final_buf;
git_off_t final_buf_size;
};
git_blame *git_blame__alloc(
git_repository *repo,
git_blame_options opts,
const char *path);
#endif

622
src/blame_git.c Normal file
View File

@ -0,0 +1,622 @@
/*
* 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 "blame_git.h"
#include "commit.h"
#include "blob.h"
#include "xdiff/xinclude.h"
/*
* Origin is refcounted and usually we keep the blob contents to be
* reused.
*/
static git_blame__origin *origin_incref(git_blame__origin *o)
{
if (o)
o->refcnt++;
return o;
}
static void origin_decref(git_blame__origin *o)
{
if (o && --o->refcnt <= 0) {
if (o->previous)
origin_decref(o->previous);
git_blob_free(o->blob);
git_commit_free(o->commit);
git__free(o);
}
}
/* Given a commit and a path in it, create a new origin structure. */
static int make_origin(git_blame__origin **out, git_commit *commit, const char *path)
{
int error = 0;
git_blame__origin *o;
o = git__calloc(1, sizeof(*o) + strlen(path) + 1);
GITERR_CHECK_ALLOC(o);
o->commit = commit;
o->refcnt = 1;
strcpy(o->path, path);
if (!(error = git_object_lookup_bypath((git_object**)&o->blob, (git_object*)commit,
path, GIT_OBJ_BLOB))) {
*out = o;
} else {
origin_decref(o);
}
return error;
}
/* Locate an existing origin or create a new one. */
int git_blame__get_origin(
git_blame__origin **out,
git_blame *blame,
git_commit *commit,
const char *path)
{
git_blame__entry *e;
for (e = blame->ent; e; e = e->next) {
if (e->suspect->commit == commit && !strcmp(e->suspect->path, path)) {
*out = origin_incref(e->suspect);
}
}
return make_origin(out, commit, path);
}
typedef struct blame_chunk_cb_data {
git_blame *blame;
git_blame__origin *target;
git_blame__origin *parent;
long tlno;
long plno;
}blame_chunk_cb_data;
static bool same_suspect(git_blame__origin *a, git_blame__origin *b)
{
if (a == b)
return true;
if (git_oid_cmp(git_commit_id(a->commit), git_commit_id(b->commit)))
return false;
return 0 == strcmp(a->path, b->path);
}
/* find the line number of the last line the target is suspected for */
static int find_last_in_target(git_blame *blame, git_blame__origin *target)
{
git_blame__entry *e;
int last_in_target = -1;
for (e=blame->ent; e; e=e->next) {
if (e->guilty || !same_suspect(e->suspect, target))
continue;
if (last_in_target < e->s_lno + e->num_lines)
last_in_target = e->s_lno + e->num_lines;
}
return last_in_target;
}
/*
* It is known that lines between tlno to same came from parent, and e
* has an overlap with that range. it also is known that parent's
* line plno corresponds to e's line tlno.
*
* <---- e ----->
* <------> (entirely within)
* <------------> (extends past)
* <------------> (starts before)
* <------------------> (entirely encloses)
*
* Split e into potentially three parts; before this chunk, the chunk
* to be blamed for the parent, and after that portion.
*/
static void split_overlap(git_blame__entry *split, git_blame__entry *e,
int tlno, int plno, int same, git_blame__origin *parent)
{
int chunk_end_lno;
if (e->s_lno < tlno) {
/* there is a pre-chunk part not blamed on the parent */
split[0].suspect = origin_incref(e->suspect);
split[0].lno = e->lno;
split[0].s_lno = e->s_lno;
split[0].num_lines = tlno - e->s_lno;
split[1].lno = e->lno + tlno - e->s_lno;
split[1].s_lno = plno;
} else {
split[1].lno = e->lno;
split[1].s_lno = plno + (e->s_lno - tlno);
}
if (same < e->s_lno + e->num_lines) {
/* there is a post-chunk part not blamed on parent */
split[2].suspect = origin_incref(e->suspect);
split[2].lno = e->lno + (same - e->s_lno);
split[2].s_lno = e->s_lno + (same - e->s_lno);
split[2].num_lines = e->s_lno + e->num_lines - same;
chunk_end_lno = split[2].lno;
} else {
chunk_end_lno = e->lno + e->num_lines;
}
split[1].num_lines = chunk_end_lno - split[1].lno;
/*
* if it turns out there is nothing to blame the parent for, forget about
* the splitting. !split[1].suspect signals this.
*/
if (split[1].num_lines < 1)
return;
split[1].suspect = origin_incref(parent);
}
/*
* Link in a new blame entry to the scoreboard. Entries that cover the same
* line range have been removed from the scoreboard previously.
*/
static void add_blame_entry(git_blame *blame, git_blame__entry *e)
{
git_blame__entry *ent, *prev = NULL;
origin_incref(e->suspect);
for (ent = blame->ent; ent && ent->lno < e->lno; ent = ent->next)
prev = ent;
/* prev, if not NULL, is the last one that is below e */
e->prev = prev;
if (prev) {
e->next = prev->next;
prev->next = e;
} else {
e->next = blame->ent;
blame->ent = e;
}
if (e->next)
e->next->prev = e;
}
/*
* src typically is on-stack; we want to copy the information in it to
* a malloced blame_entry that is already on the linked list of the scoreboard.
* The origin of dst loses a refcnt while the origin of src gains one.
*/
static void dup_entry(git_blame__entry *dst, git_blame__entry *src)
{
git_blame__entry *p, *n;
p = dst->prev;
n = dst->next;
origin_incref(src->suspect);
origin_decref(dst->suspect);
memcpy(dst, src, sizeof(*src));
dst->prev = p;
dst->next = n;
dst->score = 0;
}
/*
* split_overlap() divided an existing blame e into up to three parts in split.
* Adjust the linked list of blames in the scoreboard to reflect the split.
*/
static void split_blame(git_blame *blame, git_blame__entry *split, git_blame__entry *e)
{
git_blame__entry *new_entry;
if (split[0].suspect && split[2].suspect) {
/* The first part (reuse storage for the existing entry e */
dup_entry(e, &split[0]);
/* The last part -- me */
new_entry = git__malloc(sizeof(*new_entry));
memcpy(new_entry, &(split[2]), sizeof(git_blame__entry));
add_blame_entry(blame, new_entry);
/* ... and the middle part -- parent */
new_entry = git__malloc(sizeof(*new_entry));
memcpy(new_entry, &(split[1]), sizeof(git_blame__entry));
add_blame_entry(blame, new_entry);
} else if (!split[0].suspect && !split[2].suspect) {
/*
* The parent covers the entire area; reuse storage for e and replace it
* with the parent
*/
dup_entry(e, &split[1]);
} else if (split[0].suspect) {
/* me and then parent */
dup_entry(e, &split[0]);
new_entry = git__malloc(sizeof(*new_entry));
memcpy(new_entry, &(split[1]), sizeof(git_blame__entry));
add_blame_entry(blame, new_entry);
} else {
/* parent and then me */
dup_entry(e, &split[1]);
new_entry = git__malloc(sizeof(*new_entry));
memcpy(new_entry, &(split[2]), sizeof(git_blame__entry));
add_blame_entry(blame, new_entry);
}
}
/*
* After splitting the blame, the origins used by the on-stack blame_entry
* should lose one refcnt each.
*/
static void decref_split(git_blame__entry *split)
{
int i;
for (i=0; i<3; i++)
origin_decref(split[i].suspect);
}
/*
* Helper for blame_chunk(). blame_entry e is known to overlap with the patch
* hunk; split it and pass blame to the parent.
*/
static void blame_overlap(
git_blame *blame,
git_blame__entry *e,
int tlno,
int plno,
int same,
git_blame__origin *parent)
{
git_blame__entry split[3] = {{0}};
split_overlap(split, e, tlno, plno, same, parent);
if (split[1].suspect)
split_blame(blame, split, e);
decref_split(split);
}
/*
* Process one hunk from the patch between the current suspect for blame_entry
* e and its parent. Find and split the overlap, and pass blame to the
* overlapping part to the parent.
*/
static void blame_chunk(
git_blame *blame,
int tlno,
int plno,
int same,
git_blame__origin *target,
git_blame__origin *parent)
{
git_blame__entry *e;
for (e = blame->ent; e; e = e->next) {
if (e->guilty || !same_suspect(e->suspect, target))
continue;
if (same <= e->s_lno)
continue;
if (tlno < e->s_lno + e->num_lines) {
blame_overlap(blame, e, tlno, plno, same, parent);
}
}
}
static int my_emit(
xdfenv_t *xe,
xdchange_t *xscr,
xdemitcb_t *ecb,
xdemitconf_t const *xecfg)
{
xdchange_t *xch = xscr;
GIT_UNUSED(xe);
GIT_UNUSED(xecfg);
while (xch) {
blame_chunk_cb_data *d = ecb->priv;
blame_chunk(d->blame, d->tlno, d->plno, xch->i2, d->target, d->parent);
d->plno = xch->i1 + xch->chg1;
d->tlno = xch->i2 + xch->chg2;
xch = xch->next;
}
return 0;
}
static void trim_common_tail(mmfile_t *a, mmfile_t *b, long ctx)
{
const int blk = 1024;
long trimmed = 0, recovered = 0;
char *ap = a->ptr + a->size;
char *bp = b->ptr + b->size;
long smaller = (long)((a->size < b->size) ? a->size : b->size);
if (ctx)
return;
while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) {
trimmed += blk;
ap -= blk;
bp -= blk;
}
while (recovered < trimmed)
if (ap[recovered++] == '\n')
break;
a->size -= trimmed - recovered;
b->size -= trimmed - recovered;
}
static int diff_hunks(mmfile_t file_a, mmfile_t file_b, void *cb_data)
{
xpparam_t xpp = {0};
xdemitconf_t xecfg = {0};
xdemitcb_t ecb = {0};
xecfg.emit_func = (void(*)(void))my_emit;
ecb.priv = cb_data;
trim_common_tail(&file_a, &file_b, 0);
return xdl_diff(&file_a, &file_b, &xpp, &xecfg, &ecb);
}
static void fill_origin_blob(git_blame__origin *o, mmfile_t *file)
{
memset(file, 0, sizeof(*file));
if (o->blob) {
file->ptr = (char*)git_blob_rawcontent(o->blob);
file->size = (size_t)git_blob_rawsize(o->blob);
}
}
static int pass_blame_to_parent(
git_blame *blame,
git_blame__origin *target,
git_blame__origin *parent)
{
int last_in_target;
mmfile_t file_p, file_o;
blame_chunk_cb_data d = { blame, target, parent, 0, 0 };
last_in_target = find_last_in_target(blame, target);
if (last_in_target < 0)
return 1; /* nothing remains for this target */
fill_origin_blob(parent, &file_p);
fill_origin_blob(target, &file_o);
diff_hunks(file_p, file_o, &d);
/* The reset (i.e. anything after tlno) are the same as the parent */
blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent);
return 0;
}
static int paths_on_dup(void **old, void *new)
{
GIT_UNUSED(old);
git__free(new);
return -1;
}
static git_blame__origin* find_origin(
git_blame *blame,
git_commit *parent,
git_blame__origin *origin)
{
git_blame__origin *porigin = NULL;
git_diff *difflist = NULL;
git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
git_tree *otree=NULL, *ptree=NULL;
/* Get the trees from this commit and its parent */
if (0 != git_commit_tree(&otree, origin->commit) ||
0 != git_commit_tree(&ptree, parent))
goto cleanup;
/* Configure the diff */
diffopts.context_lines = 0;
diffopts.flags = GIT_DIFF_SKIP_BINARY_CHECK;
/* Check to see if files we're interested have changed */
diffopts.pathspec.count = blame->paths.length;
diffopts.pathspec.strings = (char**)blame->paths.contents;
if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts))
goto cleanup;
if (!git_diff_num_deltas(difflist)) {
/* No changes; copy data */
git_blame__get_origin(&porigin, blame, parent, origin->path);
} else {
git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
int i;
/* Generate a full diff between the two trees */
git_diff_free(difflist);
diffopts.pathspec.count = 0;
if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts))
goto cleanup;
/* Let diff find renames */
findopts.flags = GIT_DIFF_FIND_RENAMES;
if (0 != git_diff_find_similar(difflist, &findopts))
goto cleanup;
/* Find one that matches */
for (i=0; i<(int)git_diff_num_deltas(difflist); i++) {
const git_diff_delta *delta = git_diff_get_delta(difflist, i);
if (!git_vector_bsearch(NULL, &blame->paths, delta->new_file.path))
{
git_vector_insert_sorted(&blame->paths, (void*)git__strdup(delta->old_file.path),
paths_on_dup);
make_origin(&porigin, parent, delta->old_file.path);
}
}
}
cleanup:
git_diff_free(difflist);
git_tree_free(otree);
git_tree_free(ptree);
return porigin;
}
/*
* The blobs of origin and porigin exactly match, so everything origin is
* suspected for can be blamed on the parent.
*/
static void pass_whole_blame(git_blame *blame,
git_blame__origin *origin, git_blame__origin *porigin)
{
git_blame__entry *e;
if (!porigin->blob)
git_object_lookup((git_object**)&porigin->blob, blame->repository,
git_blob_id(origin->blob), GIT_OBJ_BLOB);
for (e=blame->ent; e; e=e->next) {
if (!same_suspect(e->suspect, origin))
continue;
origin_incref(porigin);
origin_decref(e->suspect);
e->suspect = porigin;
}
}
static void pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt)
{
git_commit *commit = origin->commit;
int i, num_parents;
git_blame__origin *sg_buf[16];
git_blame__origin *porigin, **sg_origin = sg_buf;
GIT_UNUSED(opt);
num_parents = git_commit_parentcount(commit);
if (!git_oid_cmp(git_commit_id(commit), &blame->options.oldest_commit))
/* Stop at oldest specified commit */
num_parents = 0;
if (!num_parents) {
git_oid_cpy(&blame->options.oldest_commit, git_commit_id(commit));
goto finish;
}
else if (num_parents < (int)ARRAY_SIZE(sg_buf))
memset(sg_buf, 0, sizeof(sg_buf));
else
sg_origin = git__calloc(num_parents, sizeof(*sg_origin));
for (i=0; i<num_parents; i++) {
git_commit *p;
int j, same;
if (sg_origin[i])
continue;
git_commit_parent(&p, origin->commit, i);
porigin = find_origin(blame, p, origin);
if (!porigin)
continue;
if (porigin->blob && origin->blob &&
!git_oid_cmp(git_blob_id(porigin->blob), git_blob_id(origin->blob))) {
pass_whole_blame(blame, origin, porigin);
origin_decref(porigin);
goto finish;
}
for (j = same = 0; j<i; j++)
if (sg_origin[j] &&
!git_oid_cmp(git_blob_id(sg_origin[j]->blob), git_blob_id(porigin->blob))) {
same = 1;
break;
}
if (!same)
sg_origin[i] = porigin;
else
origin_decref(porigin);
}
/* Standard blame */
for (i=0; i<num_parents; i++) {
git_blame__origin *porigin = sg_origin[i];
if (!porigin)
continue;
if (!origin->previous) {
origin_incref(porigin);
origin->previous = porigin;
}
if (pass_blame_to_parent(blame, origin, porigin))
goto finish;
}
/* TODO: optionally find moves in parents' files */
/* TODO: optionally find copies in parents' files */
finish:
for (i=0; i<num_parents; i++)
if (sg_origin[i])
origin_decref(sg_origin[i]);
if (sg_origin != sg_buf)
git__free(sg_origin);
return;
}
/*
* If two blame entries that are next to each other came from
* contiguous lines in the same origin (i.e. <commit, path> pair),
* merge them together.
*/
static void coalesce(git_blame *blame)
{
git_blame__entry *ent, *next;
for (ent=blame->ent; ent && (next = ent->next); ent = next) {
if (same_suspect(ent->suspect, next->suspect) &&
ent->guilty == next->guilty &&
ent->s_lno + ent->num_lines == next->s_lno)
{
ent->num_lines += next->num_lines;
ent->next = next->next;
if (ent->next)
ent->next->prev = ent;
origin_decref(next->suspect);
git__free(next);
ent->score = 0;
next = ent; /* again */
}
}
}
void git_blame__like_git(git_blame *blame, uint32_t opt)
{
while (true) {
git_blame__entry *ent;
git_blame__origin *suspect = NULL;
/* Find a suspect to break down */
for (ent = blame->ent; !suspect && ent; ent = ent->next)
if (!ent->guilty)
suspect = ent->suspect;
if (!suspect)
return; /* all done */
/* We'll use this suspect later in the loop, so hold on to it for now. */
origin_incref(suspect);
pass_blame(blame, suspect, opt);
/* Take responsibility for the remaining entries */
for (ent = blame->ent; ent; ent = ent->next) {
if (same_suspect(ent->suspect, suspect)) {
ent->guilty = true;
ent->is_boundary = !git_oid_cmp(
git_commit_id(suspect->commit),
&blame->options.oldest_commit);
}
}
origin_decref(suspect);
}
coalesce(blame);
}
void git_blame__free_entry(git_blame__entry *ent)
{
if (!ent) return;
origin_decref(ent->suspect);
git__free(ent);
}

20
src/blame_git.h Normal file
View File

@ -0,0 +1,20 @@
/*
* 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_blame_git__
#define INCLUDE_blame_git__
#include "blame.h"
int git_blame__get_origin(
git_blame__origin **out,
git_blame *sb,
git_commit *commit,
const char *path);
void git_blame__free_entry(git_blame__entry *ent);
void git_blame__like_git(git_blame *sb, uint32_t flags);
#endif

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