From 7acd3c8b5878d524eb9ccc00b8e3f780b2971d74 Mon Sep 17 00:00:00 2001 From: jiongle1 Date: Thu, 28 Mar 2024 17:05:04 +0800 Subject: [PATCH 1/3] commit patch 2390900 --- git-submodule.sh | 9 + git-submodule.sh.orig | 876 ++++++++++++++++++++++++++++++++++++ t/t5815-submodule-protos.sh | 43 ++ 3 files changed, 928 insertions(+) create mode 100755 git-submodule.sh.orig create mode 100755 t/t5815-submodule-protos.sh diff --git a/git-submodule.sh b/git-submodule.sh index 0462e529d93475..5f8017a1ab280b 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -17,6 +17,15 @@ OPTIONS_SPEC= . git-parse-remote require_work_tree +# Restrict ourselves to a vanilla subset of protocols; the URLs +# we get are under control of a remote repository, and we do not +# want them kicking off arbitrary git-remote-* programs. +# +# If the user has already specified a set of allowed protocols, +# we assume they know what they're doing and use that instead. +: ${GIT_ALLOW_PROTOCOL=file:git:http:https:ssh} +export GIT_ALLOW_PROTOCOL + command= branch= reference= diff --git a/git-submodule.sh.orig b/git-submodule.sh.orig new file mode 100755 index 00000000000000..0462e529d93475 --- /dev/null +++ b/git-submodule.sh.orig @@ -0,0 +1,876 @@ +#!/bin/sh +# +# git-submodules.sh: add, init, update or list git submodules +# +# Copyright (c) 2007 Lars Hjemli + +dashless=$(basename "$0" | sed -e 's/-/ /') +USAGE="[--quiet] add [-b branch] [--reference ] [--] + or: $dashless [--quiet] status [--cached] [--recursive] [--] [...] + or: $dashless [--quiet] init [--] [...] + or: $dashless [--quiet] update [--init] [-N|--no-fetch] [--rebase] [--reference ] [--merge] [--recursive] [--] [...] + or: $dashless [--quiet] summary [--cached|--files] [--summary-limit ] [commit] [--] [...] + or: $dashless [--quiet] foreach [--recursive] + or: $dashless [--quiet] sync [--] [...]" +OPTIONS_SPEC= +. git-sh-setup +. git-parse-remote +require_work_tree + +command= +branch= +reference= +cached= +files= +nofetch= +update= +prefix= + +# Resolve relative url by appending to parent's url +resolve_relative_url () +{ + remote=$(get_default_remote) + remoteurl=$(git config "remote.$remote.url") || + die "remote ($remote) does not have a url defined in .git/config" + url="$1" + remoteurl=${remoteurl%/} + while test -n "$url" + do + case "$url" in + ../*) + url="${url#../}" + remoteurl="${remoteurl%/*}" + ;; + ./*) + url="${url#./}" + ;; + *) + break;; + esac + done + echo "$remoteurl/${url%/}" +} + +# +# Get submodule info for registered submodules +# $@ = path to limit submodule list +# +module_list() +{ + git ls-files --error-unmatch --stage -- "$@" | grep '^160000 ' +} + +# +# Map submodule path to submodule name +# +# $1 = path +# +module_name() +{ + # Do we have "submodule..path = $1" defined in .gitmodules file? + re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g') + name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | + sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' ) + test -z "$name" && + die "No submodule mapping found in .gitmodules for path '$path'" + echo "$name" +} + +# +# Clone a submodule +# +# Prior to calling, cmd_update checks that a possibly existing +# path is not a git repository. +# Likewise, cmd_add checks that path does not exist at all, +# since it is the location of a new submodule. +# +module_clone() +{ + path=$1 + url=$2 + reference="$3" + + # If there already is a directory at the submodule path, + # expect it to be empty (since that is the default checkout + # action) and try to remove it. + # Note: if $path is a symlink to a directory the test will + # succeed but the rmdir will fail. We might want to fix this. + if test -d "$path" + then + rmdir "$path" 2>/dev/null || + die "Directory '$path' exists, but is neither empty nor a git repository" + fi + + test -e "$path" && + die "A file already exist at path '$path'" + + if test -n "$reference" + then + git-clone "$reference" -n "$url" "$path" + else + git-clone -n "$url" "$path" + fi || + die "Clone of '$url' into submodule path '$path' failed" +} + +# +# Add a new submodule to the working tree, .gitmodules and the index +# +# $@ = repo path +# +# optional branch is stored in global branch variable +# +cmd_add() +{ + # parse $args after "submodule ... add". + while test $# -ne 0 + do + case "$1" in + -b | --branch) + case "$2" in '') usage ;; esac + branch=$2 + shift + ;; + -q|--quiet) + GIT_QUIET=1 + ;; + --reference) + case "$2" in '') usage ;; esac + reference="--reference=$2" + shift + ;; + --reference=*) + reference="$1" + shift + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + + repo=$1 + path=$2 + + if test -z "$repo" -o -z "$path"; then + usage + fi + + # assure repo is absolute or relative to parent + case "$repo" in + ./*|../*) + # dereference source url relative to parent's url + realrepo=$(resolve_relative_url "$repo") || exit + ;; + *:*|/*) + # absolute url + realrepo=$repo + ;; + *) + die "repo URL: '$repo' must be absolute or begin with ./|../" + ;; + esac + + # normalize path: + # multiple //; leading ./; /./; /../; trailing / + path=$(printf '%s/\n' "$path" | + sed -e ' + s|//*|/|g + s|^\(\./\)*|| + s|/\./|/|g + :start + s|\([^/]*\)/\.\./|| + tstart + s|/*$|| + ') + git ls-files --error-unmatch "$path" > /dev/null 2>&1 && + die "'$path' already exists in the index" + + # perhaps the path exists and is already a git repo, else clone it + if test -e "$path" + then + if test -d "$path"/.git -o -f "$path"/.git + then + echo "Adding existing repo at '$path' to the index" + else + die "'$path' already exists and is not a valid git repo" + fi + + case "$repo" in + ./*|../*) + url=$(resolve_relative_url "$repo") || exit + ;; + *) + url="$repo" + ;; + esac + git config submodule."$path".url "$url" + else + + module_clone "$path" "$realrepo" "$reference" || exit + ( + unset GIT_DIR + cd "$path" && + # ash fails to wordsplit ${branch:+-b "$branch"...} + case "$branch" in + '') git checkout -f -q ;; + ?*) git checkout -f -q -b "$branch" "origin/$branch" ;; + esac + ) || die "Unable to checkout submodule '$path'" + fi + + git add "$path" || + die "Failed to add submodule '$path'" + + git config -f .gitmodules submodule."$path".path "$path" && + git config -f .gitmodules submodule."$path".url "$repo" && + git add .gitmodules || + die "Failed to register submodule '$path'" +} + +# +# Execute an arbitrary command sequence in each checked out +# submodule +# +# $@ = command to execute +# +cmd_foreach() +{ + # parse $args after "submodule ... foreach". + while test $# -ne 0 + do + case "$1" in + -q|--quiet) + GIT_QUIET=1 + ;; + --recursive) + recursive=1 + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + + module_list | + while read mode sha1 stage path + do + if test -e "$path"/.git + then + say "Entering '$prefix$path'" + name=$(module_name "$path") + ( + prefix="$prefix$path/" + unset GIT_DIR + cd "$path" && + eval "$@" && + if test -n "$recursive" + then + cmd_foreach "--recursive" "$@" + fi + ) || + die "Stopping at '$path'; script returned non-zero status." + fi + done +} + +# +# Register submodules in .git/config +# +# $@ = requested paths (default to all) +# +cmd_init() +{ + # parse $args after "submodule ... init". + while test $# -ne 0 + do + case "$1" in + -q|--quiet) + GIT_QUIET=1 + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + + module_list "$@" | + while read mode sha1 stage path + do + # Skip already registered paths + name=$(module_name "$path") || exit + url=$(git config submodule."$name".url) + test -z "$url" || continue + + url=$(git config -f .gitmodules submodule."$name".url) + test -z "$url" && + die "No url found for submodule path '$path' in .gitmodules" + + # Possibly a url relative to parent + case "$url" in + ./*|../*) + url=$(resolve_relative_url "$url") || exit + ;; + esac + + git config submodule."$name".url "$url" || + die "Failed to register url for submodule path '$path'" + + upd="$(git config -f .gitmodules submodule."$name".update)" + test -z "$upd" || + git config submodule."$name".update "$upd" || + die "Failed to register update mode for submodule path '$path'" + + say "Submodule '$name' ($url) registered for path '$path'" + done +} + +# +# Update each submodule path to correct revision, using clone and checkout as needed +# +# $@ = requested paths (default to all) +# +cmd_update() +{ + # parse $args after "submodule ... update". + orig_args="$@" + while test $# -ne 0 + do + case "$1" in + -q|--quiet) + shift + GIT_QUIET=1 + ;; + -i|--init) + init=1 + shift + ;; + -N|--no-fetch) + shift + nofetch=1 + ;; + -r|--rebase) + shift + update="rebase" + ;; + --reference) + case "$2" in '') usage ;; esac + reference="--reference=$2" + shift 2 + ;; + --reference=*) + reference="$1" + shift + ;; + -m|--merge) + shift + update="merge" + ;; + --recursive) + shift + recursive=1 + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + done + + if test -n "$init" + then + cmd_init "--" "$@" || return + fi + + module_list "$@" | + while read mode sha1 stage path + do + name=$(module_name "$path") || exit + url=$(git config submodule."$name".url) + update_module=$(git config submodule."$name".update) + if test -z "$url" + then + # Only mention uninitialized submodules when its + # path have been specified + test "$#" != "0" && + say "Submodule path '$path' not initialized" && + say "Maybe you want to use 'update --init'?" + continue + fi + + if ! test -d "$path"/.git -o -f "$path"/.git + then + module_clone "$path" "$url" "$reference"|| exit + subsha1= + else + subsha1=$(unset GIT_DIR; cd "$path" && + git rev-parse --verify HEAD) || + die "Unable to find current revision in submodule path '$path'" + fi + + if ! test -z "$update" + then + update_module=$update + fi + + if test "$subsha1" != "$sha1" + then + force= + if test -z "$subsha1" + then + force="-f" + fi + + if test -z "$nofetch" + then + (unset GIT_DIR; cd "$path" && + git-fetch) || + die "Unable to fetch in submodule path '$path'" + fi + + case "$update_module" in + rebase) + command="git rebase" + action="rebase" + msg="rebased onto" + ;; + merge) + command="git merge" + action="merge" + msg="merged in" + ;; + *) + command="git checkout $force -q" + action="checkout" + msg="checked out" + ;; + esac + + (unset GIT_DIR; cd "$path" && $command "$sha1") || + die "Unable to $action '$sha1' in submodule path '$path'" + say "Submodule path '$path': $msg '$sha1'" + fi + + if test -n "$recursive" + then + (unset GIT_DIR; cd "$path" && cmd_update $orig_args) || + die "Failed to recurse into submodule path '$path'" + fi + done +} + +set_name_rev () { + revname=$( ( + unset GIT_DIR + cd "$1" && { + git describe "$2" 2>/dev/null || + git describe --tags "$2" 2>/dev/null || + git describe --contains "$2" 2>/dev/null || + git describe --all --always "$2" + } + ) ) + test -z "$revname" || revname=" ($revname)" +} +# +# Show commit summary for submodules in index or working tree +# +# If '--cached' is given, show summary between index and given commit, +# or between working tree and given commit +# +# $@ = [commit (default 'HEAD'),] requested paths (default all) +# +cmd_summary() { + summary_limit=-1 + for_status= + diff_cmd=diff-index + + # parse $args after "submodule ... summary". + while test $# -ne 0 + do + case "$1" in + --cached) + cached="$1" + ;; + --files) + files="$1" + ;; + --for-status) + for_status="$1" + ;; + -n|--summary-limit) + if summary_limit=$(($2 + 0)) 2>/dev/null && test "$summary_limit" = "$2" + then + : + else + usage + fi + shift + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + + test $summary_limit = 0 && return + + if rev=$(git rev-parse -q --verify "$1^0") + then + head=$rev + shift + else + head=HEAD + fi + + if [ -n "$files" ] + then + test -n "$cached" && + die "--cached cannot be used with --files" + diff_cmd=diff-files + head= + fi + + cd_to_toplevel + # Get modified modules cared by user + modules=$(git $diff_cmd $cached --raw $head -- "$@" | + egrep '^:([0-7]* )?160000' | + while read mod_src mod_dst sha1_src sha1_dst status name + do + # Always show modules deleted or type-changed (blob<->module) + test $status = D -o $status = T && echo "$name" && continue + # Also show added or modified modules which are checked out + GIT_DIR="$name/.git" git-rev-parse --git-dir >/dev/null 2>&1 && + echo "$name" + done + ) + + test -z "$modules" && return + + git $diff_cmd $cached --raw $head -- $modules | + egrep '^:([0-7]* )?160000' | + cut -c2- | + while read mod_src mod_dst sha1_src sha1_dst status name + do + if test -z "$cached" && + test $sha1_dst = 0000000000000000000000000000000000000000 + then + case "$mod_dst" in + 160000) + sha1_dst=$(GIT_DIR="$name/.git" git rev-parse HEAD) + ;; + 100644 | 100755 | 120000) + sha1_dst=$(git hash-object $name) + ;; + 000000) + ;; # removed + *) + # unexpected type + echo >&2 "unexpected mode $mod_dst" + continue ;; + esac + fi + missing_src= + missing_dst= + + test $mod_src = 160000 && + ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_src^0 >/dev/null && + missing_src=t + + test $mod_dst = 160000 && + ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_dst^0 >/dev/null && + missing_dst=t + + total_commits= + case "$missing_src,$missing_dst" in + t,) + errmsg=" Warn: $name doesn't contain commit $sha1_src" + ;; + ,t) + errmsg=" Warn: $name doesn't contain commit $sha1_dst" + ;; + t,t) + errmsg=" Warn: $name doesn't contain commits $sha1_src and $sha1_dst" + ;; + *) + errmsg= + total_commits=$( + if test $mod_src = 160000 -a $mod_dst = 160000 + then + range="$sha1_src...$sha1_dst" + elif test $mod_src = 160000 + then + range=$sha1_src + else + range=$sha1_dst + fi + GIT_DIR="$name/.git" \ + git log --pretty=oneline --first-parent $range | wc -l + ) + total_commits=" ($(($total_commits + 0)))" + ;; + esac + + sha1_abbr_src=$(echo $sha1_src | cut -c1-7) + sha1_abbr_dst=$(echo $sha1_dst | cut -c1-7) + if test $status = T + then + if test $mod_dst = 160000 + then + echo "* $name $sha1_abbr_src(blob)->$sha1_abbr_dst(submodule)$total_commits:" + else + echo "* $name $sha1_abbr_src(submodule)->$sha1_abbr_dst(blob)$total_commits:" + fi + else + echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:" + fi + if test -n "$errmsg" + then + # Don't give error msg for modification whose dst is not submodule + # i.e. deleted or changed to blob + test $mod_dst = 160000 && echo "$errmsg" + else + if test $mod_src = 160000 -a $mod_dst = 160000 + then + limit= + test $summary_limit -gt 0 && limit="-$summary_limit" + GIT_DIR="$name/.git" \ + git log $limit --pretty='format: %m %s' \ + --first-parent $sha1_src...$sha1_dst + elif test $mod_dst = 160000 + then + GIT_DIR="$name/.git" \ + git log --pretty='format: > %s' -1 $sha1_dst + else + GIT_DIR="$name/.git" \ + git log --pretty='format: < %s' -1 $sha1_src + fi + echo + fi + echo + done | + if test -n "$for_status"; then + echo "# Modified submodules:" + echo "#" + sed -e 's|^|# |' -e 's|^# $|#|' + else + cat + fi +} +# +# List all submodules, prefixed with: +# - submodule not initialized +# + different revision checked out +# +# If --cached was specified the revision in the index will be printed +# instead of the currently checked out revision. +# +# $@ = requested paths (default to all) +# +cmd_status() +{ + # parse $args after "submodule ... status". + orig_args="$@" + while test $# -ne 0 + do + case "$1" in + -q|--quiet) + GIT_QUIET=1 + ;; + --cached) + cached=1 + ;; + --recursive) + recursive=1 + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + + module_list "$@" | + while read mode sha1 stage path + do + name=$(module_name "$path") || exit + url=$(git config submodule."$name".url) + displaypath="$prefix$path" + if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git + then + say "-$sha1 $displaypath" + continue; + fi + set_name_rev "$path" "$sha1" + if git diff-files --quiet -- "$path" + then + say " $sha1 $displaypath$revname" + else + if test -z "$cached" + then + sha1=$(unset GIT_DIR; cd "$path" && git rev-parse --verify HEAD) + set_name_rev "$path" "$sha1" + fi + say "+$sha1 $displaypath$revname" + fi + + if test -n "$recursive" + then + ( + prefix="$displaypath/" + unset GIT_DIR + cd "$path" && + cmd_status $orig_args + ) || + die "Failed to recurse into submodule path '$path'" + fi + done +} +# +# Sync remote urls for submodules +# This makes the value for remote.$remote.url match the value +# specified in .gitmodules. +# +cmd_sync() +{ + while test $# -ne 0 + do + case "$1" in + -q|--quiet) + GIT_QUIET=1 + shift + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + done + cd_to_toplevel + module_list "$@" | + while read mode sha1 stage path + do + name=$(module_name "$path") + url=$(git config -f .gitmodules --get submodule."$name".url) + + # Possibly a url relative to parent + case "$url" in + ./*|../*) + url=$(resolve_relative_url "$url") || exit + ;; + esac + + if test -e "$path"/.git + then + ( + unset GIT_DIR + cd "$path" + remote=$(get_default_remote) + say "Synchronizing submodule url for '$name'" + git config remote."$remote".url "$url" + ) + fi + done +} + +# This loop parses the command line arguments to find the +# subcommand name to dispatch. Parsing of the subcommand specific +# options are primarily done by the subcommand implementations. +# Subcommand specific options such as --branch and --cached are +# parsed here as well, for backward compatibility. + +while test $# != 0 && test -z "$command" +do + case "$1" in + add | foreach | init | update | status | summary | sync) + command=$1 + ;; + -q|--quiet) + GIT_QUIET=1 + ;; + -b|--branch) + case "$2" in + '') + usage + ;; + esac + branch="$2"; shift + ;; + --cached) + cached="$1" + ;; + --) + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift +done + +# No command word defaults to "status" +test -n "$command" || command=status + +# "-b branch" is accepted only by "add" +if test -n "$branch" && test "$command" != add +then + usage +fi + +# "--cached" is accepted only by "status" and "summary" +if test -n "$cached" && test "$command" != status -a "$command" != summary +then + usage +fi + +"cmd_$command" "$@" diff --git a/t/t5815-submodule-protos.sh b/t/t5815-submodule-protos.sh new file mode 100755 index 00000000000000..06f55a1b8a0b57 --- /dev/null +++ b/t/t5815-submodule-protos.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description='test protocol whitelisting with submodules' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-proto-disable.sh + +setup_ext_wrapper +setup_ssh_wrapper + +test_expect_success 'setup repository with submodules' ' + mkdir remote && + git init remote/repo.git && + (cd remote/repo.git && test_commit one) && + # submodule-add should probably trust what we feed it on the cmdline, + # but its implementation is overly conservative. + GIT_ALLOW_PROTOCOL=ssh git submodule add remote:repo.git ssh-module && + GIT_ALLOW_PROTOCOL=ext git submodule add "ext::fake-remote %S repo.git" ext-module && + git commit -m "add submodules" +' + +test_expect_success 'clone with recurse-submodules fails' ' + test_must_fail git clone --recurse-submodules . dst +' + +test_expect_success 'setup individual updates' ' + rm -rf dst && + git clone . dst && + git -C dst submodule init +' + +test_expect_success 'update of ssh allowed' ' + git -C dst submodule update ssh-module +' + +test_expect_success 'update of ext not allowed' ' + test_must_fail git -C dst submodule update ext-module +' + +test_expect_success 'user can override whitelist' ' + GIT_ALLOW_PROTOCOL=ext git -C dst submodule update ext-module +' + +test_done From 2f74e6f834a63503c50a559ec9d7dee6c210633a Mon Sep 17 00:00:00 2001 From: jiongle1 Date: Thu, 28 Mar 2024 17:05:08 +0800 Subject: [PATCH 2/3] commit patch 3673343 --- compat/nedmalloc/nedmalloc.c | 5 +- compat/nedmalloc/nedmalloc.c.orig | 966 +++++++++++ fast-import.c | 5 +- fast-import.c.orig | 2529 +++++++++++++++++++++++++++++ revision.c | 2 +- revision.c.orig | 1928 ++++++++++++++++++++++ 6 files changed, 5430 insertions(+), 5 deletions(-) create mode 100644 compat/nedmalloc/nedmalloc.c.orig create mode 100644 fast-import.c.orig create mode 100644 revision.c.orig diff --git a/compat/nedmalloc/nedmalloc.c b/compat/nedmalloc/nedmalloc.c index d9a17a8057c574..875b792215d2ff 100644 --- a/compat/nedmalloc/nedmalloc.c +++ b/compat/nedmalloc/nedmalloc.c @@ -954,8 +954,9 @@ char *strdup(const char *s1) { char *s2 = 0; if (s1) { - s2 = malloc(strlen(s1) + 1); - strcpy(s2, s1); + size_t len = strlen(s1) + 1; + s2 = malloc(len); + memcpy(s2, s1, len); } return s2; } diff --git a/compat/nedmalloc/nedmalloc.c.orig b/compat/nedmalloc/nedmalloc.c.orig new file mode 100644 index 00000000000000..d9a17a8057c574 --- /dev/null +++ b/compat/nedmalloc/nedmalloc.c.orig @@ -0,0 +1,966 @@ +/* Alternative malloc implementation for multiple threads without +lock contention based on dlmalloc. (C) 2005-2006 Niall Douglas + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifdef _MSC_VER +/* Enable full aliasing on MSVC */ +/*#pragma optimize("a", on)*/ +#endif + +/*#define FULLSANITYCHECKS*/ + +#include "nedmalloc.h" +#if defined(WIN32) + #include +#endif +#define MSPACES 1 +#define ONLY_MSPACES 1 +#ifndef USE_LOCKS + #define USE_LOCKS 1 +#endif +#define FOOTERS 1 /* Need to enable footers so frees lock the right mspace */ +#undef DEBUG /* dlmalloc wants DEBUG either 0 or 1 */ +#ifdef _DEBUG + #define DEBUG 1 +#else + #define DEBUG 0 +#endif +#ifdef NDEBUG /* Disable assert checking on release builds */ + #undef DEBUG +#endif +/* The default of 64Kb means we spend too much time kernel-side */ +#ifndef DEFAULT_GRANULARITY +#define DEFAULT_GRANULARITY (1*1024*1024) +#endif +/*#define USE_SPIN_LOCKS 0*/ + + +/*#define FORCEINLINE*/ +#include "malloc.c.h" +#ifdef NDEBUG /* Disable assert checking on release builds */ + #undef DEBUG +#endif + +/* The maximum concurrent threads in a pool possible */ +#ifndef MAXTHREADSINPOOL +#define MAXTHREADSINPOOL 16 +#endif +/* The maximum number of threadcaches which can be allocated */ +#ifndef THREADCACHEMAXCACHES +#define THREADCACHEMAXCACHES 256 +#endif +/* The maximum size to be allocated from the thread cache */ +#ifndef THREADCACHEMAX +#define THREADCACHEMAX 8192 +#endif +#if 0 +/* The number of cache entries for finer grained bins. This is (topbitpos(THREADCACHEMAX)-4)*2 */ +#define THREADCACHEMAXBINS ((13-4)*2) +#else +/* The number of cache entries. This is (topbitpos(THREADCACHEMAX)-4) */ +#define THREADCACHEMAXBINS (13-4) +#endif +/* Point at which the free space in a thread cache is garbage collected */ +#ifndef THREADCACHEMAXFREESPACE +#define THREADCACHEMAXFREESPACE (512*1024) +#endif + + +#ifdef WIN32 + #define TLSVAR DWORD + #define TLSALLOC(k) (*(k)=TlsAlloc(), TLS_OUT_OF_INDEXES==*(k)) + #define TLSFREE(k) (!TlsFree(k)) + #define TLSGET(k) TlsGetValue(k) + #define TLSSET(k, a) (!TlsSetValue(k, a)) + #ifdef DEBUG +static LPVOID ChkedTlsGetValue(DWORD idx) +{ + LPVOID ret=TlsGetValue(idx); + assert(S_OK==GetLastError()); + return ret; +} + #undef TLSGET + #define TLSGET(k) ChkedTlsGetValue(k) + #endif +#else + #define TLSVAR pthread_key_t + #define TLSALLOC(k) pthread_key_create(k, 0) + #define TLSFREE(k) pthread_key_delete(k) + #define TLSGET(k) pthread_getspecific(k) + #define TLSSET(k, a) pthread_setspecific(k, a) +#endif + +#if 0 +/* Only enable if testing with valgrind. Causes misoperation */ +#define mspace_malloc(p, s) malloc(s) +#define mspace_realloc(p, m, s) realloc(m, s) +#define mspace_calloc(p, n, s) calloc(n, s) +#define mspace_free(p, m) free(m) +#endif + + +#if defined(__cplusplus) +#if !defined(NO_NED_NAMESPACE) +namespace nedalloc { +#else +extern "C" { +#endif +#endif + +size_t nedblksize(void *mem) THROWSPEC +{ +#if 0 + /* Only enable if testing with valgrind. Causes misoperation */ + return THREADCACHEMAX; +#else + if(mem) + { + mchunkptr p=mem2chunk(mem); + assert(cinuse(p)); /* If this fails, someone tried to free a block twice */ + if(cinuse(p)) + return chunksize(p)-overhead_for(p); + } + return 0; +#endif +} + +void nedsetvalue(void *v) THROWSPEC { nedpsetvalue(0, v); } +void * nedmalloc(size_t size) THROWSPEC { return nedpmalloc(0, size); } +void * nedcalloc(size_t no, size_t size) THROWSPEC { return nedpcalloc(0, no, size); } +void * nedrealloc(void *mem, size_t size) THROWSPEC { return nedprealloc(0, mem, size); } +void nedfree(void *mem) THROWSPEC { nedpfree(0, mem); } +void * nedmemalign(size_t alignment, size_t bytes) THROWSPEC { return nedpmemalign(0, alignment, bytes); } +#if !NO_MALLINFO +struct mallinfo nedmallinfo(void) THROWSPEC { return nedpmallinfo(0); } +#endif +int nedmallopt(int parno, int value) THROWSPEC { return nedpmallopt(0, parno, value); } +int nedmalloc_trim(size_t pad) THROWSPEC { return nedpmalloc_trim(0, pad); } +void nedmalloc_stats() THROWSPEC { nedpmalloc_stats(0); } +size_t nedmalloc_footprint() THROWSPEC { return nedpmalloc_footprint(0); } +void **nedindependent_calloc(size_t elemsno, size_t elemsize, void **chunks) THROWSPEC { return nedpindependent_calloc(0, elemsno, elemsize, chunks); } +void **nedindependent_comalloc(size_t elems, size_t *sizes, void **chunks) THROWSPEC { return nedpindependent_comalloc(0, elems, sizes, chunks); } + +struct threadcacheblk_t; +typedef struct threadcacheblk_t threadcacheblk; +struct threadcacheblk_t +{ /* Keep less than 16 bytes on 32 bit systems and 32 bytes on 64 bit systems */ +#ifdef FULLSANITYCHECKS + unsigned int magic; +#endif + unsigned int lastUsed, size; + threadcacheblk *next, *prev; +}; +typedef struct threadcache_t +{ +#ifdef FULLSANITYCHECKS + unsigned int magic1; +#endif + int mymspace; /* Last mspace entry this thread used */ + long threadid; + unsigned int mallocs, frees, successes; + size_t freeInCache; /* How much free space is stored in this cache */ + threadcacheblk *bins[(THREADCACHEMAXBINS+1)*2]; +#ifdef FULLSANITYCHECKS + unsigned int magic2; +#endif +} threadcache; +struct nedpool_t +{ + MLOCK_T mutex; + void *uservalue; + int threads; /* Max entries in m to use */ + threadcache *caches[THREADCACHEMAXCACHES]; + TLSVAR mycache; /* Thread cache for this thread. 0 for unset, negative for use mspace-1 directly, otherwise is cache-1 */ + mstate m[MAXTHREADSINPOOL+1]; /* mspace entries for this pool */ +}; +static nedpool syspool; + +static FORCEINLINE unsigned int size2binidx(size_t _size) THROWSPEC +{ /* 8=1000 16=10000 20=10100 24=11000 32=100000 48=110000 4096=1000000000000 */ + unsigned int topbit, size=(unsigned int)(_size>>4); + /* 16=1 20=1 24=1 32=10 48=11 64=100 96=110 128=1000 4096=100000000 */ + +#if defined(__GNUC__) + topbit = sizeof(size)*__CHAR_BIT__ - 1 - __builtin_clz(size); +#elif defined(_MSC_VER) && _MSC_VER>=1300 + { + unsigned long bsrTopBit; + + _BitScanReverse(&bsrTopBit, size); + + topbit = bsrTopBit; + } +#else +#if 0 + union { + unsigned asInt[2]; + double asDouble; + }; + int n; + + asDouble = (double)size + 0.5; + topbit = (asInt[!FOX_BIGENDIAN] >> 20) - 1023; +#else + { + unsigned int x=size; + x = x | (x >> 1); + x = x | (x >> 2); + x = x | (x >> 4); + x = x | (x >> 8); + x = x | (x >>16); + x = ~x; + x = x - ((x >> 1) & 0x55555555); + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + x = (x + (x >> 4)) & 0x0F0F0F0F; + x = x + (x << 8); + x = x + (x << 16); + topbit=31 - (x >> 24); + } +#endif +#endif + return topbit; +} + + +#ifdef FULLSANITYCHECKS +static void tcsanitycheck(threadcacheblk **ptr) THROWSPEC +{ + assert((ptr[0] && ptr[1]) || (!ptr[0] && !ptr[1])); + if(ptr[0] && ptr[1]) + { + assert(nedblksize(ptr[0])>=sizeof(threadcacheblk)); + assert(nedblksize(ptr[1])>=sizeof(threadcacheblk)); + assert(*(unsigned int *) "NEDN"==ptr[0]->magic); + assert(*(unsigned int *) "NEDN"==ptr[1]->magic); + assert(!ptr[0]->prev); + assert(!ptr[1]->next); + if(ptr[0]==ptr[1]) + { + assert(!ptr[0]->next); + assert(!ptr[1]->prev); + } + } +} +static void tcfullsanitycheck(threadcache *tc) THROWSPEC +{ + threadcacheblk **tcbptr=tc->bins; + int n; + for(n=0; n<=THREADCACHEMAXBINS; n++, tcbptr+=2) + { + threadcacheblk *b, *ob=0; + tcsanitycheck(tcbptr); + for(b=tcbptr[0]; b; ob=b, b=b->next) + { + assert(*(unsigned int *) "NEDN"==b->magic); + assert(!ob || ob->next==b); + assert(!ob || b->prev==ob); + } + } +} +#endif + +static NOINLINE void RemoveCacheEntries(nedpool *p, threadcache *tc, unsigned int age) THROWSPEC +{ +#ifdef FULLSANITYCHECKS + tcfullsanitycheck(tc); +#endif + if(tc->freeInCache) + { + threadcacheblk **tcbptr=tc->bins; + int n; + for(n=0; n<=THREADCACHEMAXBINS; n++, tcbptr+=2) + { + threadcacheblk **tcb=tcbptr+1; /* come from oldest end of list */ + /*tcsanitycheck(tcbptr);*/ + for(; *tcb && tc->frees-(*tcb)->lastUsed>=age; ) + { + threadcacheblk *f=*tcb; + size_t blksize=f->size; /*nedblksize(f);*/ + assert(blksize<=nedblksize(f)); + assert(blksize); +#ifdef FULLSANITYCHECKS + assert(*(unsigned int *) "NEDN"==(*tcb)->magic); +#endif + *tcb=(*tcb)->prev; + if(*tcb) + (*tcb)->next=0; + else + *tcbptr=0; + tc->freeInCache-=blksize; + assert((long) tc->freeInCache>=0); + mspace_free(0, f); + /*tcsanitycheck(tcbptr);*/ + } + } + } +#ifdef FULLSANITYCHECKS + tcfullsanitycheck(tc); +#endif +} +static void DestroyCaches(nedpool *p) THROWSPEC +{ + if(p->caches) + { + threadcache *tc; + int n; + for(n=0; ncaches[n])) + { + tc->frees++; + RemoveCacheEntries(p, tc, 0); + assert(!tc->freeInCache); + tc->mymspace=-1; + tc->threadid=0; + mspace_free(0, tc); + p->caches[n]=0; + } + } + } +} + +static NOINLINE threadcache *AllocCache(nedpool *p) THROWSPEC +{ + threadcache *tc=0; + int n, end; + ACQUIRE_LOCK(&p->mutex); + for(n=0; ncaches[n]; n++); + if(THREADCACHEMAXCACHES==n) + { /* List exhausted, so disable for this thread */ + RELEASE_LOCK(&p->mutex); + return 0; + } + tc=p->caches[n]=(threadcache *) mspace_calloc(p->m[0], 1, sizeof(threadcache)); + if(!tc) + { + RELEASE_LOCK(&p->mutex); + return 0; + } +#ifdef FULLSANITYCHECKS + tc->magic1=*(unsigned int *)"NEDMALC1"; + tc->magic2=*(unsigned int *)"NEDMALC2"; +#endif + tc->threadid=(long)(size_t)CURRENT_THREAD; + for(end=0; p->m[end]; end++); + tc->mymspace=tc->threadid % end; + RELEASE_LOCK(&p->mutex); + if(TLSSET(p->mycache, (void *)(size_t)(n+1))) abort(); + return tc; +} + +static void *threadcache_malloc(nedpool *p, threadcache *tc, size_t *size) THROWSPEC +{ + void *ret=0; + unsigned int bestsize; + unsigned int idx=size2binidx(*size); + size_t blksize=0; + threadcacheblk *blk, **binsptr; +#ifdef FULLSANITYCHECKS + tcfullsanitycheck(tc); +#endif + /* Calculate best fit bin size */ + bestsize=1<<(idx+4); +#if 0 + /* Finer grained bin fit */ + idx<<=1; + if(*size>bestsize) + { + idx++; + bestsize+=bestsize>>1; + } + if(*size>bestsize) + { + idx++; + bestsize=1<<(4+(idx>>1)); + } +#else + if(*size>bestsize) + { + idx++; + bestsize<<=1; + } +#endif + assert(bestsize>=*size); + if(*sizebins[idx*2]; + /* Try to match close, but move up a bin if necessary */ + blk=*binsptr; + if(!blk || blk->size<*size) + { /* Bump it up a bin */ + if(idxsize; /*nedblksize(blk);*/ + assert(nedblksize(blk)>=blksize); + assert(blksize>=*size); + if(blk->next) + blk->next->prev=0; + *binsptr=blk->next; + if(!*binsptr) + binsptr[1]=0; +#ifdef FULLSANITYCHECKS + blk->magic=0; +#endif + assert(binsptr[0]!=blk && binsptr[1]!=blk); + assert(nedblksize(blk)>=sizeof(threadcacheblk) && nedblksize(blk)<=THREADCACHEMAX+CHUNK_OVERHEAD); + /*printf("malloc: %p, %p, %p, %lu\n", p, tc, blk, (long) size);*/ + ret=(void *) blk; + } + ++tc->mallocs; + if(ret) + { + assert(blksize>=*size); + ++tc->successes; + tc->freeInCache-=blksize; + assert((long) tc->freeInCache>=0); + } +#if defined(DEBUG) && 0 + if(!(tc->mallocs & 0xfff)) + { + printf("*** threadcache=%u, mallocs=%u (%f), free=%u (%f), freeInCache=%u\n", (unsigned int) tc->threadid, tc->mallocs, + (float) tc->successes/tc->mallocs, tc->frees, (float) tc->successes/tc->frees, (unsigned int) tc->freeInCache); + } +#endif +#ifdef FULLSANITYCHECKS + tcfullsanitycheck(tc); +#endif + return ret; +} +static NOINLINE void ReleaseFreeInCache(nedpool *p, threadcache *tc, int mymspace) THROWSPEC +{ + unsigned int age=THREADCACHEMAXFREESPACE/8192; + /*ACQUIRE_LOCK(&p->m[mymspace]->mutex);*/ + while(age && tc->freeInCache>=THREADCACHEMAXFREESPACE) + { + RemoveCacheEntries(p, tc, age); + /*printf("*** Removing cache entries older than %u (%u)\n", age, (unsigned int) tc->freeInCache);*/ + age>>=1; + } + /*RELEASE_LOCK(&p->m[mymspace]->mutex);*/ +} +static void threadcache_free(nedpool *p, threadcache *tc, int mymspace, void *mem, size_t size) THROWSPEC +{ + unsigned int bestsize; + unsigned int idx=size2binidx(size); + threadcacheblk **binsptr, *tck=(threadcacheblk *) mem; + assert(size>=sizeof(threadcacheblk) && size<=THREADCACHEMAX+CHUNK_OVERHEAD); +#ifdef DEBUG + { /* Make sure this is a valid memory block */ + mchunkptr p = mem2chunk(mem); + mstate fm = get_mstate_for(p); + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, p); + return; + } + } +#endif +#ifdef FULLSANITYCHECKS + tcfullsanitycheck(tc); +#endif + /* Calculate best fit bin size */ + bestsize=1<<(idx+4); +#if 0 + /* Finer grained bin fit */ + idx<<=1; + if(size>bestsize) + { + unsigned int biggerbestsize=bestsize+bestsize<<1; + if(size>=biggerbestsize) + { + idx++; + bestsize=biggerbestsize; + } + } +#endif + if(bestsize!=size) /* dlmalloc can round up, so we round down to preserve indexing */ + size=bestsize; + binsptr=&tc->bins[idx*2]; + assert(idx<=THREADCACHEMAXBINS); + if(tck==*binsptr) + { + fprintf(stderr, "Attempt to free already freed memory block %p - aborting!\n", tck); + abort(); + } +#ifdef FULLSANITYCHECKS + tck->magic=*(unsigned int *) "NEDN"; +#endif + tck->lastUsed=++tc->frees; + tck->size=(unsigned int) size; + tck->next=*binsptr; + tck->prev=0; + if(tck->next) + tck->next->prev=tck; + else + binsptr[1]=tck; + assert(!*binsptr || (*binsptr)->size==tck->size); + *binsptr=tck; + assert(tck==tc->bins[idx*2]); + assert(tc->bins[idx*2+1]==tck || binsptr[0]->next->prev==tck); + /*printf("free: %p, %p, %p, %lu\n", p, tc, mem, (long) size);*/ + tc->freeInCache+=size; +#ifdef FULLSANITYCHECKS + tcfullsanitycheck(tc); +#endif +#if 1 + if(tc->freeInCache>=THREADCACHEMAXFREESPACE) + ReleaseFreeInCache(p, tc, mymspace); +#endif +} + + + + +static NOINLINE int InitPool(nedpool *p, size_t capacity, int threads) THROWSPEC +{ /* threads is -1 for system pool */ + ensure_initialization(); + ACQUIRE_MALLOC_GLOBAL_LOCK(); + if(p->threads) goto done; + if(INITIAL_LOCK(&p->mutex)) goto err; + if(TLSALLOC(&p->mycache)) goto err; + if(!(p->m[0]=(mstate) create_mspace(capacity, 1))) goto err; + p->m[0]->extp=p; + p->threads=(threads<1 || threads>MAXTHREADSINPOOL) ? MAXTHREADSINPOOL : threads; +done: + RELEASE_MALLOC_GLOBAL_LOCK(); + return 1; +err: + if(threads<0) + abort(); /* If you can't allocate for system pool, we're screwed */ + DestroyCaches(p); + if(p->m[0]) + { + destroy_mspace(p->m[0]); + p->m[0]=0; + } + if(p->mycache) + { + if(TLSFREE(p->mycache)) abort(); + p->mycache=0; + } + RELEASE_MALLOC_GLOBAL_LOCK(); + return 0; +} +static NOINLINE mstate FindMSpace(nedpool *p, threadcache *tc, int *lastUsed, size_t size) THROWSPEC +{ /* Gets called when thread's last used mspace is in use. The strategy + is to run through the list of all available mspaces looking for an + unlocked one and if we fail, we create a new one so long as we don't + exceed p->threads */ + int n, end; + for(n=end=*lastUsed+1; p->m[n]; end=++n) + { + if(TRY_LOCK(&p->m[n]->mutex)) goto found; + } + for(n=0; n<*lastUsed && p->m[n]; n++) + { + if(TRY_LOCK(&p->m[n]->mutex)) goto found; + } + if(endthreads) + { + mstate temp; + if(!(temp=(mstate) create_mspace(size, 1))) + goto badexit; + /* Now we're ready to modify the lists, we lock */ + ACQUIRE_LOCK(&p->mutex); + while(p->m[end] && endthreads) + end++; + if(end>=p->threads) + { /* Drat, must destroy it now */ + RELEASE_LOCK(&p->mutex); + destroy_mspace((mspace) temp); + goto badexit; + } + /* We really want to make sure this goes into memory now but we + have to be careful of breaking aliasing rules, so write it twice */ + *((volatile struct malloc_state **) &p->m[end])=p->m[end]=temp; + ACQUIRE_LOCK(&p->m[end]->mutex); + /*printf("Created mspace idx %d\n", end);*/ + RELEASE_LOCK(&p->mutex); + n=end; + goto found; + } + /* Let it lock on the last one it used */ +badexit: + ACQUIRE_LOCK(&p->m[*lastUsed]->mutex); + return p->m[*lastUsed]; +found: + *lastUsed=n; + if(tc) + tc->mymspace=n; + else + { + if(TLSSET(p->mycache, (void *)(size_t)(-(n+1)))) abort(); + } + return p->m[n]; +} + +nedpool *nedcreatepool(size_t capacity, int threads) THROWSPEC +{ + nedpool *ret; + if(!(ret=(nedpool *) nedpcalloc(0, 1, sizeof(nedpool)))) return 0; + if(!InitPool(ret, capacity, threads)) + { + nedpfree(0, ret); + return 0; + } + return ret; +} +void neddestroypool(nedpool *p) THROWSPEC +{ + int n; + ACQUIRE_LOCK(&p->mutex); + DestroyCaches(p); + for(n=0; p->m[n]; n++) + { + destroy_mspace(p->m[n]); + p->m[n]=0; + } + RELEASE_LOCK(&p->mutex); + if(TLSFREE(p->mycache)) abort(); + nedpfree(0, p); +} + +void nedpsetvalue(nedpool *p, void *v) THROWSPEC +{ + if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); } + p->uservalue=v; +} +void *nedgetvalue(nedpool **p, void *mem) THROWSPEC +{ + nedpool *np=0; + mchunkptr mcp=mem2chunk(mem); + mstate fm; + if(!(is_aligned(chunk2mem(mcp))) && mcp->head != FENCEPOST_HEAD) return 0; + if(!cinuse(mcp)) return 0; + if(!next_pinuse(mcp)) return 0; + if(!is_mmapped(mcp) && !pinuse(mcp)) + { + if(next_chunk(prev_chunk(mcp))!=mcp) return 0; + } + fm=get_mstate_for(mcp); + if(!ok_magic(fm)) return 0; + if(!ok_address(fm, mcp)) return 0; + if(!fm->extp) return 0; + np=(nedpool *) fm->extp; + if(p) *p=np; + return np->uservalue; +} + +void neddisablethreadcache(nedpool *p) THROWSPEC +{ + int mycache; + if(!p) + { + p=&syspool; + if(!syspool.threads) InitPool(&syspool, 0, -1); + } + mycache=(int)(size_t) TLSGET(p->mycache); + if(!mycache) + { /* Set to mspace 0 */ + if(TLSSET(p->mycache, (void *)-1)) abort(); + } + else if(mycache>0) + { /* Set to last used mspace */ + threadcache *tc=p->caches[mycache-1]; +#if defined(DEBUG) + printf("Threadcache utilisation: %lf%% in cache with %lf%% lost to other threads\n", + 100.0*tc->successes/tc->mallocs, 100.0*((double) tc->mallocs-tc->frees)/tc->mallocs); +#endif + if(TLSSET(p->mycache, (void *)(size_t)(-tc->mymspace))) abort(); + tc->frees++; + RemoveCacheEntries(p, tc, 0); + assert(!tc->freeInCache); + tc->mymspace=-1; + tc->threadid=0; + mspace_free(0, p->caches[mycache-1]); + p->caches[mycache-1]=0; + } +} + +#define GETMSPACE(m,p,tc,ms,s,action) \ + do \ + { \ + mstate m = GetMSpace((p),(tc),(ms),(s)); \ + action; \ + RELEASE_LOCK(&m->mutex); \ + } while (0) + +static FORCEINLINE mstate GetMSpace(nedpool *p, threadcache *tc, int mymspace, size_t size) THROWSPEC +{ /* Returns a locked and ready for use mspace */ + mstate m=p->m[mymspace]; + assert(m); + if(!TRY_LOCK(&p->m[mymspace]->mutex)) m=FindMSpace(p, tc, &mymspace, size);\ + /*assert(IS_LOCKED(&p->m[mymspace]->mutex));*/ + return m; +} +static FORCEINLINE void GetThreadCache(nedpool **p, threadcache **tc, int *mymspace, size_t *size) THROWSPEC +{ + int mycache; + if(size && *sizemycache); + if(mycache>0) + { + *tc=(*p)->caches[mycache-1]; + *mymspace=(*tc)->mymspace; + } + else if(!mycache) + { + *tc=AllocCache(*p); + if(!*tc) + { /* Disable */ + if(TLSSET((*p)->mycache, (void *)-1)) abort(); + *mymspace=0; + } + else + *mymspace=(*tc)->mymspace; + } + else + { + *tc=0; + *mymspace=-mycache-1; + } + assert(*mymspace>=0); + assert((long)(size_t)CURRENT_THREAD==(*tc)->threadid); +#ifdef FULLSANITYCHECKS + if(*tc) + { + if(*(unsigned int *)"NEDMALC1"!=(*tc)->magic1 || *(unsigned int *)"NEDMALC2"!=(*tc)->magic2) + { + abort(); + } + } +#endif +} + +void * nedpmalloc(nedpool *p, size_t size) THROWSPEC +{ + void *ret=0; + threadcache *tc; + int mymspace; + GetThreadCache(&p, &tc, &mymspace, &size); +#if THREADCACHEMAX + if(tc && size<=THREADCACHEMAX) + { /* Use the thread cache */ + ret=threadcache_malloc(p, tc, &size); + } +#endif + if(!ret) + { /* Use this thread's mspace */ + GETMSPACE(m, p, tc, mymspace, size, + ret=mspace_malloc(m, size)); + } + return ret; +} +void * nedpcalloc(nedpool *p, size_t no, size_t size) THROWSPEC +{ + size_t rsize=size*no; + void *ret=0; + threadcache *tc; + int mymspace; + GetThreadCache(&p, &tc, &mymspace, &rsize); +#if THREADCACHEMAX + if(tc && rsize<=THREADCACHEMAX) + { /* Use the thread cache */ + if((ret=threadcache_malloc(p, tc, &rsize))) + memset(ret, 0, rsize); + } +#endif + if(!ret) + { /* Use this thread's mspace */ + GETMSPACE(m, p, tc, mymspace, rsize, + ret=mspace_calloc(m, 1, rsize)); + } + return ret; +} +void * nedprealloc(nedpool *p, void *mem, size_t size) THROWSPEC +{ + void *ret=0; + threadcache *tc; + int mymspace; + if(!mem) return nedpmalloc(p, size); + GetThreadCache(&p, &tc, &mymspace, &size); +#if THREADCACHEMAX + if(tc && size && size<=THREADCACHEMAX) + { /* Use the thread cache */ + size_t memsize=nedblksize(mem); + assert(memsize); + if((ret=threadcache_malloc(p, tc, &size))) + { + memcpy(ret, mem, memsizem[n]; n++) + { + struct mallinfo t=mspace_mallinfo(p->m[n]); + ret.arena+=t.arena; + ret.ordblks+=t.ordblks; + ret.hblkhd+=t.hblkhd; + ret.usmblks+=t.usmblks; + ret.uordblks+=t.uordblks; + ret.fordblks+=t.fordblks; + ret.keepcost+=t.keepcost; + } + return ret; +} +#endif +int nedpmallopt(nedpool *p, int parno, int value) THROWSPEC +{ + return mspace_mallopt(parno, value); +} +int nedpmalloc_trim(nedpool *p, size_t pad) THROWSPEC +{ + int n, ret=0; + if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); } + for(n=0; p->m[n]; n++) + { + ret+=mspace_trim(p->m[n], pad); + } + return ret; +} +void nedpmalloc_stats(nedpool *p) THROWSPEC +{ + int n; + if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); } + for(n=0; p->m[n]; n++) + { + mspace_malloc_stats(p->m[n]); + } +} +size_t nedpmalloc_footprint(nedpool *p) THROWSPEC +{ + size_t ret=0; + int n; + if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); } + for(n=0; p->m[n]; n++) + { + ret+=mspace_footprint(p->m[n]); + } + return ret; +} +void **nedpindependent_calloc(nedpool *p, size_t elemsno, size_t elemsize, void **chunks) THROWSPEC +{ + void **ret; + threadcache *tc; + int mymspace; + GetThreadCache(&p, &tc, &mymspace, &elemsize); + GETMSPACE(m, p, tc, mymspace, elemsno*elemsize, + ret=mspace_independent_calloc(m, elemsno, elemsize, chunks)); + return ret; +} +void **nedpindependent_comalloc(nedpool *p, size_t elems, size_t *sizes, void **chunks) THROWSPEC +{ + void **ret; + threadcache *tc; + int mymspace; + size_t i, *adjustedsizes=(size_t *) alloca(elems*sizeof(size_t)); + if(!adjustedsizes) return 0; + for(i=0; i' sp when lf)? + 'committer' sp name sp '<' email '>' sp when lf + commit_msg + ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? + ('merge' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)* + file_change* + lf?; + commit_msg ::= data; + + file_change ::= file_clr + | file_del + | file_rnm + | file_cpy + | file_obm + | file_inm; + file_clr ::= 'deleteall' lf; + file_del ::= 'D' sp path_str lf; + file_rnm ::= 'R' sp path_str sp path_str lf; + file_cpy ::= 'C' sp path_str sp path_str lf; + file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf; + file_inm ::= 'M' sp mode sp 'inline' sp path_str lf + data; + + new_tag ::= 'tag' sp tag_str lf + 'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf + ('tagger' sp name sp '<' email '>' sp when lf)? + tag_msg; + tag_msg ::= data; + + reset_branch ::= 'reset' sp ref_str lf + ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? + lf?; + + checkpoint ::= 'checkpoint' lf + lf?; + + progress ::= 'progress' sp not_lf* lf + lf?; + + # note: the first idnum in a stream should be 1 and subsequent + # idnums should not have gaps between values as this will cause + # the stream parser to reserve space for the gapped values. An + # idnum can be updated in the future to a new object by issuing + # a new mark directive with the old idnum. + # + mark ::= 'mark' sp idnum lf; + data ::= (delimited_data | exact_data) + lf?; + + # note: delim may be any string but must not contain lf. + # data_line may contain any data but must not be exactly + # delim. + delimited_data ::= 'data' sp '<<' delim lf + (data_line lf)* + delim lf; + + # note: declen indicates the length of binary_data in bytes. + # declen does not include the lf preceding the binary data. + # + exact_data ::= 'data' sp declen lf + binary_data; + + # note: quoted strings are C-style quoting supporting \c for + # common escapes of 'c' (e..g \n, \t, \\, \") or \nnn where nnn + # is the signed byte value in octal. Note that the only + # characters which must actually be escaped to protect the + # stream formatting is: \, " and LF. Otherwise these values + # are UTF8. + # + ref_str ::= ref; + sha1exp_str ::= sha1exp; + tag_str ::= tag; + path_str ::= path | '"' quoted(path) '"' ; + mode ::= '100644' | '644' + | '100755' | '755' + | '120000' + ; + + declen ::= # unsigned 32 bit value, ascii base10 notation; + bigint ::= # unsigned integer value, ascii base10 notation; + binary_data ::= # file content, not interpreted; + + when ::= raw_when | rfc2822_when; + raw_when ::= ts sp tz; + rfc2822_when ::= # Valid RFC 2822 date and time; + + sp ::= # ASCII space character; + lf ::= # ASCII newline (LF) character; + + # note: a colon (':') must precede the numerical value assigned to + # an idnum. This is to distinguish it from a ref or tag name as + # GIT does not permit ':' in ref or tag strings. + # + idnum ::= ':' bigint; + path ::= # GIT style file path, e.g. "a/b/c"; + ref ::= # GIT ref name, e.g. "refs/heads/MOZ_GECKO_EXPERIMENT"; + tag ::= # GIT tag name, e.g. "FIREFOX_1_5"; + sha1exp ::= # Any valid GIT SHA1 expression; + hexsha1 ::= # SHA1 in hexadecimal format; + + # note: name and email are UTF8 strings, however name must not + # contain '<' or lf and email must not contain any of the + # following: '<', '>', lf. + # + name ::= # valid GIT author/committer name; + email ::= # valid GIT author/committer email; + ts ::= # time since the epoch in seconds, ascii base10 notation; + tz ::= # GIT style timezone; + + # note: comments may appear anywhere in the input, except + # within a data command. Any form of the data command + # always escapes the related input from comment processing. + # + # In case it is not clear, the '#' that starts the comment + # must be the first character on that line (an lf + # preceded it). + # + comment ::= '#' not_lf* lf; + not_lf ::= # Any byte that is not ASCII newline (LF); +*/ + +#include "builtin.h" +#include "cache.h" +#include "object.h" +#include "blob.h" +#include "tree.h" +#include "commit.h" +#include "delta.h" +#include "pack.h" +#include "refs.h" +#include "csum-file.h" +#include "quote.h" +#include "exec_cmd.h" + +#define PACK_ID_BITS 16 +#define MAX_PACK_ID ((1<name); + + fprintf(rpt, " status :"); + if (b->active) + fputs(" active", rpt); + if (b->branch_tree.tree) + fputs(" loaded", rpt); + if (is_null_sha1(b->branch_tree.versions[1].sha1)) + fputs(" dirty", rpt); + fputc('\n', rpt); + + fprintf(rpt, " tip commit : %s\n", sha1_to_hex(b->sha1)); + fprintf(rpt, " old tree : %s\n", sha1_to_hex(b->branch_tree.versions[0].sha1)); + fprintf(rpt, " cur tree : %s\n", sha1_to_hex(b->branch_tree.versions[1].sha1)); + fprintf(rpt, " commit clock: %" PRIuMAX "\n", b->last_commit); + + fputs(" last pack : ", rpt); + if (b->pack_id < MAX_PACK_ID) + fprintf(rpt, "%u", b->pack_id); + fputc('\n', rpt); + + fputc('\n', rpt); +} + +static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *); + +static void write_crash_report(const char *err) +{ + char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid()); + FILE *rpt = fopen(loc, "w"); + struct branch *b; + unsigned long lu; + struct recent_command *rc; + + if (!rpt) { + error("can't write crash report %s: %s", loc, strerror(errno)); + return; + } + + fprintf(stderr, "fast-import: dumping crash report to %s\n", loc); + + fprintf(rpt, "fast-import crash report:\n"); + fprintf(rpt, " fast-import process: %"PRIuMAX"\n", (uintmax_t) getpid()); + fprintf(rpt, " parent process : %"PRIuMAX"\n", (uintmax_t) getppid()); + fprintf(rpt, " at %s\n", show_date(time(NULL), 0, DATE_LOCAL)); + fputc('\n', rpt); + + fputs("fatal: ", rpt); + fputs(err, rpt); + fputc('\n', rpt); + + fputc('\n', rpt); + fputs("Most Recent Commands Before Crash\n", rpt); + fputs("---------------------------------\n", rpt); + for (rc = cmd_hist.next; rc != &cmd_hist; rc = rc->next) { + if (rc->next == &cmd_hist) + fputs("* ", rpt); + else + fputs(" ", rpt); + fputs(rc->buf, rpt); + fputc('\n', rpt); + } + + fputc('\n', rpt); + fputs("Active Branch LRU\n", rpt); + fputs("-----------------\n", rpt); + fprintf(rpt, " active_branches = %lu cur, %lu max\n", + cur_active_branches, + max_active_branches); + fputc('\n', rpt); + fputs(" pos clock name\n", rpt); + fputs(" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n", rpt); + for (b = active_branches, lu = 0; b; b = b->active_next_branch) + fprintf(rpt, " %2lu) %6" PRIuMAX" %s\n", + ++lu, b->last_commit, b->name); + + fputc('\n', rpt); + fputs("Inactive Branches\n", rpt); + fputs("-----------------\n", rpt); + for (lu = 0; lu < branch_table_sz; lu++) { + for (b = branch_table[lu]; b; b = b->table_next_branch) + write_branch_report(rpt, b); + } + + if (first_tag) { + struct tag *tg; + fputc('\n', rpt); + fputs("Annotated Tags\n", rpt); + fputs("--------------\n", rpt); + for (tg = first_tag; tg; tg = tg->next_tag) { + fputs(sha1_to_hex(tg->sha1), rpt); + fputc(' ', rpt); + fputs(tg->name, rpt); + fputc('\n', rpt); + } + } + + fputc('\n', rpt); + fputs("Marks\n", rpt); + fputs("-----\n", rpt); + if (mark_file) + fprintf(rpt, " exported to %s\n", mark_file); + else + dump_marks_helper(rpt, 0, marks); + + fputc('\n', rpt); + fputs("-------------------\n", rpt); + fputs("END OF CRASH REPORT\n", rpt); + fclose(rpt); +} + +static void end_packfile(void); +static void unkeep_all_packs(void); +static void dump_marks(void); + +static NORETURN void die_nicely(const char *err, va_list params) +{ + static int zombie; + char message[2 * PATH_MAX]; + + vsnprintf(message, sizeof(message), err, params); + fputs("fatal: ", stderr); + fputs(message, stderr); + fputc('\n', stderr); + + if (!zombie) { + zombie = 1; + write_crash_report(message); + end_packfile(); + unkeep_all_packs(); + dump_marks(); + } + exit(128); +} + +static void alloc_objects(unsigned int cnt) +{ + struct object_entry_pool *b; + + b = xmalloc(sizeof(struct object_entry_pool) + + cnt * sizeof(struct object_entry)); + b->next_pool = blocks; + b->next_free = b->entries; + b->end = b->entries + cnt; + blocks = b; + alloc_count += cnt; +} + +static struct object_entry *new_object(unsigned char *sha1) +{ + struct object_entry *e; + + if (blocks->next_free == blocks->end) + alloc_objects(object_entry_alloc); + + e = blocks->next_free++; + hashcpy(e->sha1, sha1); + return e; +} + +static struct object_entry *find_object(unsigned char *sha1) +{ + unsigned int h = sha1[0] << 8 | sha1[1]; + struct object_entry *e; + for (e = object_table[h]; e; e = e->next) + if (!hashcmp(sha1, e->sha1)) + return e; + return NULL; +} + +static struct object_entry *insert_object(unsigned char *sha1) +{ + unsigned int h = sha1[0] << 8 | sha1[1]; + struct object_entry *e = object_table[h]; + struct object_entry *p = NULL; + + while (e) { + if (!hashcmp(sha1, e->sha1)) + return e; + p = e; + e = e->next; + } + + e = new_object(sha1); + e->next = NULL; + e->offset = 0; + if (p) + p->next = e; + else + object_table[h] = e; + return e; +} + +static unsigned int hc_str(const char *s, size_t len) +{ + unsigned int r = 0; + while (len-- > 0) + r = r * 31 + *s++; + return r; +} + +static void *pool_alloc(size_t len) +{ + struct mem_pool *p; + void *r; + + /* round up to a 'uintmax_t' alignment */ + if (len & (sizeof(uintmax_t) - 1)) + len += sizeof(uintmax_t) - (len & (sizeof(uintmax_t) - 1)); + + for (p = mem_pool; p; p = p->next_pool) + if ((p->end - p->next_free >= len)) + break; + + if (!p) { + if (len >= (mem_pool_alloc/2)) { + total_allocd += len; + return xmalloc(len); + } + total_allocd += sizeof(struct mem_pool) + mem_pool_alloc; + p = xmalloc(sizeof(struct mem_pool) + mem_pool_alloc); + p->next_pool = mem_pool; + p->next_free = (char *) p->space; + p->end = p->next_free + mem_pool_alloc; + mem_pool = p; + } + + r = p->next_free; + p->next_free += len; + return r; +} + +static void *pool_calloc(size_t count, size_t size) +{ + size_t len = count * size; + void *r = pool_alloc(len); + memset(r, 0, len); + return r; +} + +static char *pool_strdup(const char *s) +{ + char *r = pool_alloc(strlen(s) + 1); + strcpy(r, s); + return r; +} + +static void insert_mark(uintmax_t idnum, struct object_entry *oe) +{ + struct mark_set *s = marks; + while ((idnum >> s->shift) >= 1024) { + s = pool_calloc(1, sizeof(struct mark_set)); + s->shift = marks->shift + 10; + s->data.sets[0] = marks; + marks = s; + } + while (s->shift) { + uintmax_t i = idnum >> s->shift; + idnum -= i << s->shift; + if (!s->data.sets[i]) { + s->data.sets[i] = pool_calloc(1, sizeof(struct mark_set)); + s->data.sets[i]->shift = s->shift - 10; + } + s = s->data.sets[i]; + } + if (!s->data.marked[idnum]) + marks_set_count++; + s->data.marked[idnum] = oe; +} + +static struct object_entry *find_mark(uintmax_t idnum) +{ + uintmax_t orig_idnum = idnum; + struct mark_set *s = marks; + struct object_entry *oe = NULL; + if ((idnum >> s->shift) < 1024) { + while (s && s->shift) { + uintmax_t i = idnum >> s->shift; + idnum -= i << s->shift; + s = s->data.sets[i]; + } + if (s) + oe = s->data.marked[idnum]; + } + if (!oe) + die("mark :%" PRIuMAX " not declared", orig_idnum); + return oe; +} + +static struct atom_str *to_atom(const char *s, unsigned short len) +{ + unsigned int hc = hc_str(s, len) % atom_table_sz; + struct atom_str *c; + + for (c = atom_table[hc]; c; c = c->next_atom) + if (c->str_len == len && !strncmp(s, c->str_dat, len)) + return c; + + c = pool_alloc(sizeof(struct atom_str) + len + 1); + c->str_len = len; + strncpy(c->str_dat, s, len); + c->str_dat[len] = 0; + c->next_atom = atom_table[hc]; + atom_table[hc] = c; + atom_cnt++; + return c; +} + +static struct branch *lookup_branch(const char *name) +{ + unsigned int hc = hc_str(name, strlen(name)) % branch_table_sz; + struct branch *b; + + for (b = branch_table[hc]; b; b = b->table_next_branch) + if (!strcmp(name, b->name)) + return b; + return NULL; +} + +static struct branch *new_branch(const char *name) +{ + unsigned int hc = hc_str(name, strlen(name)) % branch_table_sz; + struct branch *b = lookup_branch(name); + + if (b) + die("Invalid attempt to create duplicate branch: %s", name); + switch (check_ref_format(name)) { + case 0: break; /* its valid */ + case CHECK_REF_FORMAT_ONELEVEL: + break; /* valid, but too few '/', allow anyway */ + default: + die("Branch name doesn't conform to GIT standards: %s", name); + } + + b = pool_calloc(1, sizeof(struct branch)); + b->name = pool_strdup(name); + b->table_next_branch = branch_table[hc]; + b->branch_tree.versions[0].mode = S_IFDIR; + b->branch_tree.versions[1].mode = S_IFDIR; + b->active = 0; + b->pack_id = MAX_PACK_ID; + branch_table[hc] = b; + branch_count++; + return b; +} + +static unsigned int hc_entries(unsigned int cnt) +{ + cnt = cnt & 7 ? (cnt / 8) + 1 : cnt / 8; + return cnt < avail_tree_table_sz ? cnt : avail_tree_table_sz - 1; +} + +static struct tree_content *new_tree_content(unsigned int cnt) +{ + struct avail_tree_content *f, *l = NULL; + struct tree_content *t; + unsigned int hc = hc_entries(cnt); + + for (f = avail_tree_table[hc]; f; l = f, f = f->next_avail) + if (f->entry_capacity >= cnt) + break; + + if (f) { + if (l) + l->next_avail = f->next_avail; + else + avail_tree_table[hc] = f->next_avail; + } else { + cnt = cnt & 7 ? ((cnt / 8) + 1) * 8 : cnt; + f = pool_alloc(sizeof(*t) + sizeof(t->entries[0]) * cnt); + f->entry_capacity = cnt; + } + + t = (struct tree_content*)f; + t->entry_count = 0; + t->delta_depth = 0; + return t; +} + +static void release_tree_entry(struct tree_entry *e); +static void release_tree_content(struct tree_content *t) +{ + struct avail_tree_content *f = (struct avail_tree_content*)t; + unsigned int hc = hc_entries(f->entry_capacity); + f->next_avail = avail_tree_table[hc]; + avail_tree_table[hc] = f; +} + +static void release_tree_content_recursive(struct tree_content *t) +{ + unsigned int i; + for (i = 0; i < t->entry_count; i++) + release_tree_entry(t->entries[i]); + release_tree_content(t); +} + +static struct tree_content *grow_tree_content( + struct tree_content *t, + int amt) +{ + struct tree_content *r = new_tree_content(t->entry_count + amt); + r->entry_count = t->entry_count; + r->delta_depth = t->delta_depth; + memcpy(r->entries,t->entries,t->entry_count*sizeof(t->entries[0])); + release_tree_content(t); + return r; +} + +static struct tree_entry *new_tree_entry(void) +{ + struct tree_entry *e; + + if (!avail_tree_entry) { + unsigned int n = tree_entry_alloc; + total_allocd += n * sizeof(struct tree_entry); + avail_tree_entry = e = xmalloc(n * sizeof(struct tree_entry)); + while (n-- > 1) { + *((void**)e) = e + 1; + e++; + } + *((void**)e) = NULL; + } + + e = avail_tree_entry; + avail_tree_entry = *((void**)e); + return e; +} + +static void release_tree_entry(struct tree_entry *e) +{ + if (e->tree) + release_tree_content_recursive(e->tree); + *((void**)e) = avail_tree_entry; + avail_tree_entry = e; +} + +static struct tree_content *dup_tree_content(struct tree_content *s) +{ + struct tree_content *d; + struct tree_entry *a, *b; + unsigned int i; + + if (!s) + return NULL; + d = new_tree_content(s->entry_count); + for (i = 0; i < s->entry_count; i++) { + a = s->entries[i]; + b = new_tree_entry(); + memcpy(b, a, sizeof(*a)); + if (a->tree && is_null_sha1(b->versions[1].sha1)) + b->tree = dup_tree_content(a->tree); + else + b->tree = NULL; + d->entries[i] = b; + } + d->entry_count = s->entry_count; + d->delta_depth = s->delta_depth; + + return d; +} + +static void start_packfile(void) +{ + static char tmpfile[PATH_MAX]; + struct packed_git *p; + struct pack_header hdr; + int pack_fd; + + pack_fd = odb_mkstemp(tmpfile, sizeof(tmpfile), + "pack/tmp_pack_XXXXXX"); + p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2); + strcpy(p->pack_name, tmpfile); + p->pack_fd = pack_fd; + + hdr.hdr_signature = htonl(PACK_SIGNATURE); + hdr.hdr_version = htonl(2); + hdr.hdr_entries = 0; + write_or_die(p->pack_fd, &hdr, sizeof(hdr)); + + pack_data = p; + pack_size = sizeof(hdr); + object_count = 0; + + all_packs = xrealloc(all_packs, sizeof(*all_packs) * (pack_id + 1)); + all_packs[pack_id] = p; +} + +static int oecmp (const void *a_, const void *b_) +{ + struct object_entry *a = *((struct object_entry**)a_); + struct object_entry *b = *((struct object_entry**)b_); + return hashcmp(a->sha1, b->sha1); +} + +static char *create_index(void) +{ + static char tmpfile[PATH_MAX]; + git_SHA_CTX ctx; + struct sha1file *f; + struct object_entry **idx, **c, **last, *e; + struct object_entry_pool *o; + uint32_t array[256]; + int i, idx_fd; + + /* Build the sorted table of object IDs. */ + idx = xmalloc(object_count * sizeof(struct object_entry*)); + c = idx; + for (o = blocks; o; o = o->next_pool) + for (e = o->next_free; e-- != o->entries;) + if (pack_id == e->pack_id) + *c++ = e; + last = idx + object_count; + if (c != last) + die("internal consistency error creating the index"); + qsort(idx, object_count, sizeof(struct object_entry*), oecmp); + + /* Generate the fan-out array. */ + c = idx; + for (i = 0; i < 256; i++) { + struct object_entry **next = c; + while (next < last) { + if ((*next)->sha1[0] != i) + break; + next++; + } + array[i] = htonl(next - idx); + c = next; + } + + idx_fd = odb_mkstemp(tmpfile, sizeof(tmpfile), + "pack/tmp_idx_XXXXXX"); + f = sha1fd(idx_fd, tmpfile); + sha1write(f, array, 256 * sizeof(int)); + git_SHA1_Init(&ctx); + for (c = idx; c != last; c++) { + uint32_t offset = htonl((*c)->offset); + sha1write(f, &offset, 4); + sha1write(f, (*c)->sha1, sizeof((*c)->sha1)); + git_SHA1_Update(&ctx, (*c)->sha1, 20); + } + sha1write(f, pack_data->sha1, sizeof(pack_data->sha1)); + sha1close(f, NULL, CSUM_FSYNC); + free(idx); + git_SHA1_Final(pack_data->sha1, &ctx); + return tmpfile; +} + +static char *keep_pack(char *curr_index_name) +{ + static char name[PATH_MAX]; + static const char *keep_msg = "fast-import"; + int keep_fd; + + keep_fd = odb_pack_keep(name, sizeof(name), pack_data->sha1); + if (keep_fd < 0) + die_errno("cannot create keep file"); + write_or_die(keep_fd, keep_msg, strlen(keep_msg)); + if (close(keep_fd)) + die_errno("failed to write keep file"); + + snprintf(name, sizeof(name), "%s/pack/pack-%s.pack", + get_object_directory(), sha1_to_hex(pack_data->sha1)); + if (move_temp_to_file(pack_data->pack_name, name)) + die("cannot store pack file"); + + snprintf(name, sizeof(name), "%s/pack/pack-%s.idx", + get_object_directory(), sha1_to_hex(pack_data->sha1)); + if (move_temp_to_file(curr_index_name, name)) + die("cannot store index file"); + return name; +} + +static void unkeep_all_packs(void) +{ + static char name[PATH_MAX]; + int k; + + for (k = 0; k < pack_id; k++) { + struct packed_git *p = all_packs[k]; + snprintf(name, sizeof(name), "%s/pack/pack-%s.keep", + get_object_directory(), sha1_to_hex(p->sha1)); + unlink_or_warn(name); + } +} + +static void end_packfile(void) +{ + struct packed_git *old_p = pack_data, *new_p; + + clear_delta_base_cache(); + if (object_count) { + char *idx_name; + int i; + struct branch *b; + struct tag *t; + + close_pack_windows(pack_data); + fixup_pack_header_footer(pack_data->pack_fd, pack_data->sha1, + pack_data->pack_name, object_count, + NULL, 0); + close(pack_data->pack_fd); + idx_name = keep_pack(create_index()); + + /* Register the packfile with core git's machinery. */ + new_p = add_packed_git(idx_name, strlen(idx_name), 1); + if (!new_p) + die("core git rejected index %s", idx_name); + all_packs[pack_id] = new_p; + install_packed_git(new_p); + + /* Print the boundary */ + if (pack_edges) { + fprintf(pack_edges, "%s:", new_p->pack_name); + for (i = 0; i < branch_table_sz; i++) { + for (b = branch_table[i]; b; b = b->table_next_branch) { + if (b->pack_id == pack_id) + fprintf(pack_edges, " %s", sha1_to_hex(b->sha1)); + } + } + for (t = first_tag; t; t = t->next_tag) { + if (t->pack_id == pack_id) + fprintf(pack_edges, " %s", sha1_to_hex(t->sha1)); + } + fputc('\n', pack_edges); + fflush(pack_edges); + } + + pack_id++; + } + else { + close(old_p->pack_fd); + unlink_or_warn(old_p->pack_name); + } + free(old_p); + + /* We can't carry a delta across packfiles. */ + strbuf_release(&last_blob.data); + last_blob.offset = 0; + last_blob.depth = 0; +} + +static void cycle_packfile(void) +{ + end_packfile(); + start_packfile(); +} + +static size_t encode_header( + enum object_type type, + size_t size, + unsigned char *hdr) +{ + int n = 1; + unsigned char c; + + if (type < OBJ_COMMIT || type > OBJ_REF_DELTA) + die("bad type %d", type); + + c = (type << 4) | (size & 15); + size >>= 4; + while (size) { + *hdr++ = c | 0x80; + c = size & 0x7f; + size >>= 7; + n++; + } + *hdr = c; + return n; +} + +static int store_object( + enum object_type type, + struct strbuf *dat, + struct last_object *last, + unsigned char *sha1out, + uintmax_t mark) +{ + void *out, *delta; + struct object_entry *e; + unsigned char hdr[96]; + unsigned char sha1[20]; + unsigned long hdrlen, deltalen; + git_SHA_CTX c; + z_stream s; + + hdrlen = sprintf((char *)hdr,"%s %lu", typename(type), + (unsigned long)dat->len) + 1; + git_SHA1_Init(&c); + git_SHA1_Update(&c, hdr, hdrlen); + git_SHA1_Update(&c, dat->buf, dat->len); + git_SHA1_Final(sha1, &c); + if (sha1out) + hashcpy(sha1out, sha1); + + e = insert_object(sha1); + if (mark) + insert_mark(mark, e); + if (e->offset) { + duplicate_count_by_type[type]++; + return 1; + } else if (find_sha1_pack(sha1, packed_git)) { + e->type = type; + e->pack_id = MAX_PACK_ID; + e->offset = 1; /* just not zero! */ + duplicate_count_by_type[type]++; + return 1; + } + + if (last && last->data.buf && last->depth < max_depth) { + delta = diff_delta(last->data.buf, last->data.len, + dat->buf, dat->len, + &deltalen, 0); + if (delta && deltalen >= dat->len) { + free(delta); + delta = NULL; + } + } else + delta = NULL; + + memset(&s, 0, sizeof(s)); + deflateInit(&s, pack_compression_level); + if (delta) { + s.next_in = delta; + s.avail_in = deltalen; + } else { + s.next_in = (void *)dat->buf; + s.avail_in = dat->len; + } + s.avail_out = deflateBound(&s, s.avail_in); + s.next_out = out = xmalloc(s.avail_out); + while (deflate(&s, Z_FINISH) == Z_OK) + /* nothing */; + deflateEnd(&s); + + /* Determine if we should auto-checkpoint. */ + if ((pack_size + 60 + s.total_out) > max_packsize + || (pack_size + 60 + s.total_out) < pack_size) { + + /* This new object needs to *not* have the current pack_id. */ + e->pack_id = pack_id + 1; + cycle_packfile(); + + /* We cannot carry a delta into the new pack. */ + if (delta) { + free(delta); + delta = NULL; + + memset(&s, 0, sizeof(s)); + deflateInit(&s, pack_compression_level); + s.next_in = (void *)dat->buf; + s.avail_in = dat->len; + s.avail_out = deflateBound(&s, s.avail_in); + s.next_out = out = xrealloc(out, s.avail_out); + while (deflate(&s, Z_FINISH) == Z_OK) + /* nothing */; + deflateEnd(&s); + } + } + + e->type = type; + e->pack_id = pack_id; + e->offset = pack_size; + object_count++; + object_count_by_type[type]++; + + if (delta) { + unsigned long ofs = e->offset - last->offset; + unsigned pos = sizeof(hdr) - 1; + + delta_count_by_type[type]++; + e->depth = last->depth + 1; + + hdrlen = encode_header(OBJ_OFS_DELTA, deltalen, hdr); + write_or_die(pack_data->pack_fd, hdr, hdrlen); + pack_size += hdrlen; + + hdr[pos] = ofs & 127; + while (ofs >>= 7) + hdr[--pos] = 128 | (--ofs & 127); + write_or_die(pack_data->pack_fd, hdr + pos, sizeof(hdr) - pos); + pack_size += sizeof(hdr) - pos; + } else { + e->depth = 0; + hdrlen = encode_header(type, dat->len, hdr); + write_or_die(pack_data->pack_fd, hdr, hdrlen); + pack_size += hdrlen; + } + + write_or_die(pack_data->pack_fd, out, s.total_out); + pack_size += s.total_out; + + free(out); + free(delta); + if (last) { + if (last->no_swap) { + last->data = *dat; + } else { + strbuf_swap(&last->data, dat); + } + last->offset = e->offset; + last->depth = e->depth; + } + return 0; +} + +/* All calls must be guarded by find_object() or find_mark() to + * ensure the 'struct object_entry' passed was written by this + * process instance. We unpack the entry by the offset, avoiding + * the need for the corresponding .idx file. This unpacking rule + * works because we only use OBJ_REF_DELTA within the packfiles + * created by fast-import. + * + * oe must not be NULL. Such an oe usually comes from giving + * an unknown SHA-1 to find_object() or an undefined mark to + * find_mark(). Callers must test for this condition and use + * the standard read_sha1_file() when it happens. + * + * oe->pack_id must not be MAX_PACK_ID. Such an oe is usually from + * find_mark(), where the mark was reloaded from an existing marks + * file and is referencing an object that this fast-import process + * instance did not write out to a packfile. Callers must test for + * this condition and use read_sha1_file() instead. + */ +static void *gfi_unpack_entry( + struct object_entry *oe, + unsigned long *sizep) +{ + enum object_type type; + struct packed_git *p = all_packs[oe->pack_id]; + if (p == pack_data && p->pack_size < (pack_size + 20)) { + /* The object is stored in the packfile we are writing to + * and we have modified it since the last time we scanned + * back to read a previously written object. If an old + * window covered [p->pack_size, p->pack_size + 20) its + * data is stale and is not valid. Closing all windows + * and updating the packfile length ensures we can read + * the newly written data. + */ + close_pack_windows(p); + + /* We have to offer 20 bytes additional on the end of + * the packfile as the core unpacker code assumes the + * footer is present at the file end and must promise + * at least 20 bytes within any window it maps. But + * we don't actually create the footer here. + */ + p->pack_size = pack_size + 20; + } + return unpack_entry(p, oe->offset, &type, sizep); +} + +static const char *get_mode(const char *str, uint16_t *modep) +{ + unsigned char c; + uint16_t mode = 0; + + while ((c = *str++) != ' ') { + if (c < '0' || c > '7') + return NULL; + mode = (mode << 3) + (c - '0'); + } + *modep = mode; + return str; +} + +static void load_tree(struct tree_entry *root) +{ + unsigned char *sha1 = root->versions[1].sha1; + struct object_entry *myoe; + struct tree_content *t; + unsigned long size; + char *buf; + const char *c; + + root->tree = t = new_tree_content(8); + if (is_null_sha1(sha1)) + return; + + myoe = find_object(sha1); + if (myoe && myoe->pack_id != MAX_PACK_ID) { + if (myoe->type != OBJ_TREE) + die("Not a tree: %s", sha1_to_hex(sha1)); + t->delta_depth = myoe->depth; + buf = gfi_unpack_entry(myoe, &size); + if (!buf) + die("Can't load tree %s", sha1_to_hex(sha1)); + } else { + enum object_type type; + buf = read_sha1_file(sha1, &type, &size); + if (!buf || type != OBJ_TREE) + die("Can't load tree %s", sha1_to_hex(sha1)); + } + + c = buf; + while (c != (buf + size)) { + struct tree_entry *e = new_tree_entry(); + + if (t->entry_count == t->entry_capacity) + root->tree = t = grow_tree_content(t, t->entry_count); + t->entries[t->entry_count++] = e; + + e->tree = NULL; + c = get_mode(c, &e->versions[1].mode); + if (!c) + die("Corrupt mode in %s", sha1_to_hex(sha1)); + e->versions[0].mode = e->versions[1].mode; + e->name = to_atom(c, strlen(c)); + c += e->name->str_len + 1; + hashcpy(e->versions[0].sha1, (unsigned char *)c); + hashcpy(e->versions[1].sha1, (unsigned char *)c); + c += 20; + } + free(buf); +} + +static int tecmp0 (const void *_a, const void *_b) +{ + struct tree_entry *a = *((struct tree_entry**)_a); + struct tree_entry *b = *((struct tree_entry**)_b); + return base_name_compare( + a->name->str_dat, a->name->str_len, a->versions[0].mode, + b->name->str_dat, b->name->str_len, b->versions[0].mode); +} + +static int tecmp1 (const void *_a, const void *_b) +{ + struct tree_entry *a = *((struct tree_entry**)_a); + struct tree_entry *b = *((struct tree_entry**)_b); + return base_name_compare( + a->name->str_dat, a->name->str_len, a->versions[1].mode, + b->name->str_dat, b->name->str_len, b->versions[1].mode); +} + +static void mktree(struct tree_content *t, int v, struct strbuf *b) +{ + size_t maxlen = 0; + unsigned int i; + + if (!v) + qsort(t->entries,t->entry_count,sizeof(t->entries[0]),tecmp0); + else + qsort(t->entries,t->entry_count,sizeof(t->entries[0]),tecmp1); + + for (i = 0; i < t->entry_count; i++) { + if (t->entries[i]->versions[v].mode) + maxlen += t->entries[i]->name->str_len + 34; + } + + strbuf_reset(b); + strbuf_grow(b, maxlen); + for (i = 0; i < t->entry_count; i++) { + struct tree_entry *e = t->entries[i]; + if (!e->versions[v].mode) + continue; + strbuf_addf(b, "%o %s%c", (unsigned int)e->versions[v].mode, + e->name->str_dat, '\0'); + strbuf_add(b, e->versions[v].sha1, 20); + } +} + +static void store_tree(struct tree_entry *root) +{ + struct tree_content *t = root->tree; + unsigned int i, j, del; + struct last_object lo = { STRBUF_INIT, 0, 0, /* no_swap */ 1 }; + struct object_entry *le; + + if (!is_null_sha1(root->versions[1].sha1)) + return; + + for (i = 0; i < t->entry_count; i++) { + if (t->entries[i]->tree) + store_tree(t->entries[i]); + } + + le = find_object(root->versions[0].sha1); + if (S_ISDIR(root->versions[0].mode) && le && le->pack_id == pack_id) { + mktree(t, 0, &old_tree); + lo.data = old_tree; + lo.offset = le->offset; + lo.depth = t->delta_depth; + } + + mktree(t, 1, &new_tree); + store_object(OBJ_TREE, &new_tree, &lo, root->versions[1].sha1, 0); + + t->delta_depth = lo.depth; + for (i = 0, j = 0, del = 0; i < t->entry_count; i++) { + struct tree_entry *e = t->entries[i]; + if (e->versions[1].mode) { + e->versions[0].mode = e->versions[1].mode; + hashcpy(e->versions[0].sha1, e->versions[1].sha1); + t->entries[j++] = e; + } else { + release_tree_entry(e); + del++; + } + } + t->entry_count -= del; +} + +static int tree_content_set( + struct tree_entry *root, + const char *p, + const unsigned char *sha1, + const uint16_t mode, + struct tree_content *subtree) +{ + struct tree_content *t = root->tree; + const char *slash1; + unsigned int i, n; + struct tree_entry *e; + + slash1 = strchr(p, '/'); + if (slash1) + n = slash1 - p; + else + n = strlen(p); + if (!n) + die("Empty path component found in input"); + if (!slash1 && !S_ISDIR(mode) && subtree) + die("Non-directories cannot have subtrees"); + + for (i = 0; i < t->entry_count; i++) { + e = t->entries[i]; + if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) { + if (!slash1) { + if (!S_ISDIR(mode) + && e->versions[1].mode == mode + && !hashcmp(e->versions[1].sha1, sha1)) + return 0; + e->versions[1].mode = mode; + hashcpy(e->versions[1].sha1, sha1); + if (e->tree) + release_tree_content_recursive(e->tree); + e->tree = subtree; + hashclr(root->versions[1].sha1); + return 1; + } + if (!S_ISDIR(e->versions[1].mode)) { + e->tree = new_tree_content(8); + e->versions[1].mode = S_IFDIR; + } + if (!e->tree) + load_tree(e); + if (tree_content_set(e, slash1 + 1, sha1, mode, subtree)) { + hashclr(root->versions[1].sha1); + return 1; + } + return 0; + } + } + + if (t->entry_count == t->entry_capacity) + root->tree = t = grow_tree_content(t, t->entry_count); + e = new_tree_entry(); + e->name = to_atom(p, n); + e->versions[0].mode = 0; + hashclr(e->versions[0].sha1); + t->entries[t->entry_count++] = e; + if (slash1) { + e->tree = new_tree_content(8); + e->versions[1].mode = S_IFDIR; + tree_content_set(e, slash1 + 1, sha1, mode, subtree); + } else { + e->tree = subtree; + e->versions[1].mode = mode; + hashcpy(e->versions[1].sha1, sha1); + } + hashclr(root->versions[1].sha1); + return 1; +} + +static int tree_content_remove( + struct tree_entry *root, + const char *p, + struct tree_entry *backup_leaf) +{ + struct tree_content *t = root->tree; + const char *slash1; + unsigned int i, n; + struct tree_entry *e; + + slash1 = strchr(p, '/'); + if (slash1) + n = slash1 - p; + else + n = strlen(p); + + for (i = 0; i < t->entry_count; i++) { + e = t->entries[i]; + if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) { + if (!slash1 || !S_ISDIR(e->versions[1].mode)) + goto del_entry; + if (!e->tree) + load_tree(e); + if (tree_content_remove(e, slash1 + 1, backup_leaf)) { + for (n = 0; n < e->tree->entry_count; n++) { + if (e->tree->entries[n]->versions[1].mode) { + hashclr(root->versions[1].sha1); + return 1; + } + } + backup_leaf = NULL; + goto del_entry; + } + return 0; + } + } + return 0; + +del_entry: + if (backup_leaf) + memcpy(backup_leaf, e, sizeof(*backup_leaf)); + else if (e->tree) + release_tree_content_recursive(e->tree); + e->tree = NULL; + e->versions[1].mode = 0; + hashclr(e->versions[1].sha1); + hashclr(root->versions[1].sha1); + return 1; +} + +static int tree_content_get( + struct tree_entry *root, + const char *p, + struct tree_entry *leaf) +{ + struct tree_content *t = root->tree; + const char *slash1; + unsigned int i, n; + struct tree_entry *e; + + slash1 = strchr(p, '/'); + if (slash1) + n = slash1 - p; + else + n = strlen(p); + + for (i = 0; i < t->entry_count; i++) { + e = t->entries[i]; + if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) { + if (!slash1) { + memcpy(leaf, e, sizeof(*leaf)); + if (e->tree && is_null_sha1(e->versions[1].sha1)) + leaf->tree = dup_tree_content(e->tree); + else + leaf->tree = NULL; + return 1; + } + if (!S_ISDIR(e->versions[1].mode)) + return 0; + if (!e->tree) + load_tree(e); + return tree_content_get(e, slash1 + 1, leaf); + } + } + return 0; +} + +static int update_branch(struct branch *b) +{ + static const char *msg = "fast-import"; + struct ref_lock *lock; + unsigned char old_sha1[20]; + + if (is_null_sha1(b->sha1)) + return 0; + if (read_ref(b->name, old_sha1)) + hashclr(old_sha1); + lock = lock_any_ref_for_update(b->name, old_sha1, 0); + if (!lock) + return error("Unable to lock %s", b->name); + if (!force_update && !is_null_sha1(old_sha1)) { + struct commit *old_cmit, *new_cmit; + + old_cmit = lookup_commit_reference_gently(old_sha1, 0); + new_cmit = lookup_commit_reference_gently(b->sha1, 0); + if (!old_cmit || !new_cmit) { + unlock_ref(lock); + return error("Branch %s is missing commits.", b->name); + } + + if (!in_merge_bases(old_cmit, &new_cmit, 1)) { + unlock_ref(lock); + warning("Not updating %s" + " (new tip %s does not contain %s)", + b->name, sha1_to_hex(b->sha1), sha1_to_hex(old_sha1)); + return -1; + } + } + if (write_ref_sha1(lock, b->sha1, msg) < 0) + return error("Unable to update %s", b->name); + return 0; +} + +static void dump_branches(void) +{ + unsigned int i; + struct branch *b; + + for (i = 0; i < branch_table_sz; i++) { + for (b = branch_table[i]; b; b = b->table_next_branch) + failure |= update_branch(b); + } +} + +static void dump_tags(void) +{ + static const char *msg = "fast-import"; + struct tag *t; + struct ref_lock *lock; + char ref_name[PATH_MAX]; + + for (t = first_tag; t; t = t->next_tag) { + sprintf(ref_name, "tags/%s", t->name); + lock = lock_ref_sha1(ref_name, NULL); + if (!lock || write_ref_sha1(lock, t->sha1, msg) < 0) + failure |= error("Unable to update %s", ref_name); + } +} + +static void dump_marks_helper(FILE *f, + uintmax_t base, + struct mark_set *m) +{ + uintmax_t k; + if (m->shift) { + for (k = 0; k < 1024; k++) { + if (m->data.sets[k]) + dump_marks_helper(f, (base + k) << m->shift, + m->data.sets[k]); + } + } else { + for (k = 0; k < 1024; k++) { + if (m->data.marked[k]) + fprintf(f, ":%" PRIuMAX " %s\n", base + k, + sha1_to_hex(m->data.marked[k]->sha1)); + } + } +} + +static void dump_marks(void) +{ + static struct lock_file mark_lock; + int mark_fd; + FILE *f; + + if (!mark_file) + return; + + mark_fd = hold_lock_file_for_update(&mark_lock, mark_file, 0); + if (mark_fd < 0) { + failure |= error("Unable to write marks file %s: %s", + mark_file, strerror(errno)); + return; + } + + f = fdopen(mark_fd, "w"); + if (!f) { + int saved_errno = errno; + rollback_lock_file(&mark_lock); + failure |= error("Unable to write marks file %s: %s", + mark_file, strerror(saved_errno)); + return; + } + + /* + * Since the lock file was fdopen()'ed, it should not be close()'ed. + * Assign -1 to the lock file descriptor so that commit_lock_file() + * won't try to close() it. + */ + mark_lock.fd = -1; + + dump_marks_helper(f, 0, marks); + if (ferror(f) || fclose(f)) { + int saved_errno = errno; + rollback_lock_file(&mark_lock); + failure |= error("Unable to write marks file %s: %s", + mark_file, strerror(saved_errno)); + return; + } + + if (commit_lock_file(&mark_lock)) { + int saved_errno = errno; + rollback_lock_file(&mark_lock); + failure |= error("Unable to commit marks file %s: %s", + mark_file, strerror(saved_errno)); + return; + } +} + +static int read_next_command(void) +{ + static int stdin_eof = 0; + + if (stdin_eof) { + unread_command_buf = 0; + return EOF; + } + + do { + if (unread_command_buf) { + unread_command_buf = 0; + } else { + struct recent_command *rc; + + strbuf_detach(&command_buf, NULL); + stdin_eof = strbuf_getline(&command_buf, stdin, '\n'); + if (stdin_eof) + return EOF; + + rc = rc_free; + if (rc) + rc_free = rc->next; + else { + rc = cmd_hist.next; + cmd_hist.next = rc->next; + cmd_hist.next->prev = &cmd_hist; + free(rc->buf); + } + + rc->buf = command_buf.buf; + rc->prev = cmd_tail; + rc->next = cmd_hist.prev; + rc->prev->next = rc; + cmd_tail = rc; + } + } while (command_buf.buf[0] == '#'); + + return 0; +} + +static void skip_optional_lf(void) +{ + int term_char = fgetc(stdin); + if (term_char != '\n' && term_char != EOF) + ungetc(term_char, stdin); +} + +static void parse_mark(void) +{ + if (!prefixcmp(command_buf.buf, "mark :")) { + next_mark = strtoumax(command_buf.buf + 6, NULL, 10); + read_next_command(); + } + else + next_mark = 0; +} + +static void parse_data(struct strbuf *sb) +{ + strbuf_reset(sb); + + if (prefixcmp(command_buf.buf, "data ")) + die("Expected 'data n' command, found: %s", command_buf.buf); + + if (!prefixcmp(command_buf.buf + 5, "<<")) { + char *term = xstrdup(command_buf.buf + 5 + 2); + size_t term_len = command_buf.len - 5 - 2; + + strbuf_detach(&command_buf, NULL); + for (;;) { + if (strbuf_getline(&command_buf, stdin, '\n') == EOF) + die("EOF in data (terminator '%s' not found)", term); + if (term_len == command_buf.len + && !strcmp(term, command_buf.buf)) + break; + strbuf_addbuf(sb, &command_buf); + strbuf_addch(sb, '\n'); + } + free(term); + } + else { + size_t n = 0, length; + + length = strtoul(command_buf.buf + 5, NULL, 10); + + while (n < length) { + size_t s = strbuf_fread(sb, length - n, stdin); + if (!s && feof(stdin)) + die("EOF in data (%lu bytes remaining)", + (unsigned long)(length - n)); + n += s; + } + } + + skip_optional_lf(); +} + +static int validate_raw_date(const char *src, char *result, int maxlen) +{ + const char *orig_src = src; + char *endp; + unsigned long num; + + errno = 0; + + num = strtoul(src, &endp, 10); + /* NEEDSWORK: perhaps check for reasonable values? */ + if (errno || endp == src || *endp != ' ') + return -1; + + src = endp + 1; + if (*src != '-' && *src != '+') + return -1; + + num = strtoul(src + 1, &endp, 10); + if (errno || endp == src + 1 || *endp || (endp - orig_src) >= maxlen || + 1400 < num) + return -1; + + strcpy(result, orig_src); + return 0; +} + +static char *parse_ident(const char *buf) +{ + const char *gt; + size_t name_len; + char *ident; + + gt = strrchr(buf, '>'); + if (!gt) + die("Missing > in ident string: %s", buf); + gt++; + if (*gt != ' ') + die("Missing space after > in ident string: %s", buf); + gt++; + name_len = gt - buf; + ident = xmalloc(name_len + 24); + strncpy(ident, buf, name_len); + + switch (whenspec) { + case WHENSPEC_RAW: + if (validate_raw_date(gt, ident + name_len, 24) < 0) + die("Invalid raw date \"%s\" in ident: %s", gt, buf); + break; + case WHENSPEC_RFC2822: + if (parse_date(gt, ident + name_len, 24) < 0) + die("Invalid rfc2822 date \"%s\" in ident: %s", gt, buf); + break; + case WHENSPEC_NOW: + if (strcmp("now", gt)) + die("Date in ident must be 'now': %s", buf); + datestamp(ident + name_len, 24); + break; + } + + return ident; +} + +static void parse_new_blob(void) +{ + static struct strbuf buf = STRBUF_INIT; + + read_next_command(); + parse_mark(); + parse_data(&buf); + store_object(OBJ_BLOB, &buf, &last_blob, NULL, next_mark); +} + +static void unload_one_branch(void) +{ + while (cur_active_branches + && cur_active_branches >= max_active_branches) { + uintmax_t min_commit = ULONG_MAX; + struct branch *e, *l = NULL, *p = NULL; + + for (e = active_branches; e; e = e->active_next_branch) { + if (e->last_commit < min_commit) { + p = l; + min_commit = e->last_commit; + } + l = e; + } + + if (p) { + e = p->active_next_branch; + p->active_next_branch = e->active_next_branch; + } else { + e = active_branches; + active_branches = e->active_next_branch; + } + e->active = 0; + e->active_next_branch = NULL; + if (e->branch_tree.tree) { + release_tree_content_recursive(e->branch_tree.tree); + e->branch_tree.tree = NULL; + } + cur_active_branches--; + } +} + +static void load_branch(struct branch *b) +{ + load_tree(&b->branch_tree); + if (!b->active) { + b->active = 1; + b->active_next_branch = active_branches; + active_branches = b; + cur_active_branches++; + branch_load_count++; + } +} + +static void file_change_m(struct branch *b) +{ + const char *p = command_buf.buf + 2; + static struct strbuf uq = STRBUF_INIT; + const char *endp; + struct object_entry *oe = oe; + unsigned char sha1[20]; + uint16_t mode, inline_data = 0; + + p = get_mode(p, &mode); + if (!p) + die("Corrupt mode: %s", command_buf.buf); + switch (mode) { + case 0644: + case 0755: + mode |= S_IFREG; + case S_IFREG | 0644: + case S_IFREG | 0755: + case S_IFLNK: + case S_IFGITLINK: + /* ok */ + break; + default: + die("Corrupt mode: %s", command_buf.buf); + } + + if (*p == ':') { + char *x; + oe = find_mark(strtoumax(p + 1, &x, 10)); + hashcpy(sha1, oe->sha1); + p = x; + } else if (!prefixcmp(p, "inline")) { + inline_data = 1; + p += 6; + } else { + if (get_sha1_hex(p, sha1)) + die("Invalid SHA1: %s", command_buf.buf); + oe = find_object(sha1); + p += 40; + } + if (*p++ != ' ') + die("Missing space after SHA1: %s", command_buf.buf); + + strbuf_reset(&uq); + if (!unquote_c_style(&uq, p, &endp)) { + if (*endp) + die("Garbage after path in: %s", command_buf.buf); + p = uq.buf; + } + + if (S_ISGITLINK(mode)) { + if (inline_data) + die("Git links cannot be specified 'inline': %s", + command_buf.buf); + else if (oe) { + if (oe->type != OBJ_COMMIT) + die("Not a commit (actually a %s): %s", + typename(oe->type), command_buf.buf); + } + /* + * Accept the sha1 without checking; it expected to be in + * another repository. + */ + } else if (inline_data) { + static struct strbuf buf = STRBUF_INIT; + + if (p != uq.buf) { + strbuf_addstr(&uq, p); + p = uq.buf; + } + read_next_command(); + parse_data(&buf); + store_object(OBJ_BLOB, &buf, &last_blob, sha1, 0); + } else if (oe) { + if (oe->type != OBJ_BLOB) + die("Not a blob (actually a %s): %s", + typename(oe->type), command_buf.buf); + } else { + enum object_type type = sha1_object_info(sha1, NULL); + if (type < 0) + die("Blob not found: %s", command_buf.buf); + if (type != OBJ_BLOB) + die("Not a blob (actually a %s): %s", + typename(type), command_buf.buf); + } + + tree_content_set(&b->branch_tree, p, sha1, mode, NULL); +} + +static void file_change_d(struct branch *b) +{ + const char *p = command_buf.buf + 2; + static struct strbuf uq = STRBUF_INIT; + const char *endp; + + strbuf_reset(&uq); + if (!unquote_c_style(&uq, p, &endp)) { + if (*endp) + die("Garbage after path in: %s", command_buf.buf); + p = uq.buf; + } + tree_content_remove(&b->branch_tree, p, NULL); +} + +static void file_change_cr(struct branch *b, int rename) +{ + const char *s, *d; + static struct strbuf s_uq = STRBUF_INIT; + static struct strbuf d_uq = STRBUF_INIT; + const char *endp; + struct tree_entry leaf; + + s = command_buf.buf + 2; + strbuf_reset(&s_uq); + if (!unquote_c_style(&s_uq, s, &endp)) { + if (*endp != ' ') + die("Missing space after source: %s", command_buf.buf); + } else { + endp = strchr(s, ' '); + if (!endp) + die("Missing space after source: %s", command_buf.buf); + strbuf_add(&s_uq, s, endp - s); + } + s = s_uq.buf; + + endp++; + if (!*endp) + die("Missing dest: %s", command_buf.buf); + + d = endp; + strbuf_reset(&d_uq); + if (!unquote_c_style(&d_uq, d, &endp)) { + if (*endp) + die("Garbage after dest in: %s", command_buf.buf); + d = d_uq.buf; + } + + memset(&leaf, 0, sizeof(leaf)); + if (rename) + tree_content_remove(&b->branch_tree, s, &leaf); + else + tree_content_get(&b->branch_tree, s, &leaf); + if (!leaf.versions[1].mode) + die("Path %s not in branch", s); + tree_content_set(&b->branch_tree, d, + leaf.versions[1].sha1, + leaf.versions[1].mode, + leaf.tree); +} + +static void file_change_deleteall(struct branch *b) +{ + release_tree_content_recursive(b->branch_tree.tree); + hashclr(b->branch_tree.versions[0].sha1); + hashclr(b->branch_tree.versions[1].sha1); + load_tree(&b->branch_tree); +} + +static void parse_from_commit(struct branch *b, char *buf, unsigned long size) +{ + if (!buf || size < 46) + die("Not a valid commit: %s", sha1_to_hex(b->sha1)); + if (memcmp("tree ", buf, 5) + || get_sha1_hex(buf + 5, b->branch_tree.versions[1].sha1)) + die("The commit %s is corrupt", sha1_to_hex(b->sha1)); + hashcpy(b->branch_tree.versions[0].sha1, + b->branch_tree.versions[1].sha1); +} + +static void parse_from_existing(struct branch *b) +{ + if (is_null_sha1(b->sha1)) { + hashclr(b->branch_tree.versions[0].sha1); + hashclr(b->branch_tree.versions[1].sha1); + } else { + unsigned long size; + char *buf; + + buf = read_object_with_reference(b->sha1, + commit_type, &size, b->sha1); + parse_from_commit(b, buf, size); + free(buf); + } +} + +static int parse_from(struct branch *b) +{ + const char *from; + struct branch *s; + + if (prefixcmp(command_buf.buf, "from ")) + return 0; + + if (b->branch_tree.tree) { + release_tree_content_recursive(b->branch_tree.tree); + b->branch_tree.tree = NULL; + } + + from = strchr(command_buf.buf, ' ') + 1; + s = lookup_branch(from); + if (b == s) + die("Can't create a branch from itself: %s", b->name); + else if (s) { + unsigned char *t = s->branch_tree.versions[1].sha1; + hashcpy(b->sha1, s->sha1); + hashcpy(b->branch_tree.versions[0].sha1, t); + hashcpy(b->branch_tree.versions[1].sha1, t); + } else if (*from == ':') { + uintmax_t idnum = strtoumax(from + 1, NULL, 10); + struct object_entry *oe = find_mark(idnum); + if (oe->type != OBJ_COMMIT) + die("Mark :%" PRIuMAX " not a commit", idnum); + hashcpy(b->sha1, oe->sha1); + if (oe->pack_id != MAX_PACK_ID) { + unsigned long size; + char *buf = gfi_unpack_entry(oe, &size); + parse_from_commit(b, buf, size); + free(buf); + } else + parse_from_existing(b); + } else if (!get_sha1(from, b->sha1)) + parse_from_existing(b); + else + die("Invalid ref name or SHA1 expression: %s", from); + + read_next_command(); + return 1; +} + +static struct hash_list *parse_merge(unsigned int *count) +{ + struct hash_list *list = NULL, *n, *e = e; + const char *from; + struct branch *s; + + *count = 0; + while (!prefixcmp(command_buf.buf, "merge ")) { + from = strchr(command_buf.buf, ' ') + 1; + n = xmalloc(sizeof(*n)); + s = lookup_branch(from); + if (s) + hashcpy(n->sha1, s->sha1); + else if (*from == ':') { + uintmax_t idnum = strtoumax(from + 1, NULL, 10); + struct object_entry *oe = find_mark(idnum); + if (oe->type != OBJ_COMMIT) + die("Mark :%" PRIuMAX " not a commit", idnum); + hashcpy(n->sha1, oe->sha1); + } else if (!get_sha1(from, n->sha1)) { + unsigned long size; + char *buf = read_object_with_reference(n->sha1, + commit_type, &size, n->sha1); + if (!buf || size < 46) + die("Not a valid commit: %s", from); + free(buf); + } else + die("Invalid ref name or SHA1 expression: %s", from); + + n->next = NULL; + if (list) + e->next = n; + else + list = n; + e = n; + (*count)++; + read_next_command(); + } + return list; +} + +static void parse_new_commit(void) +{ + static struct strbuf msg = STRBUF_INIT; + struct branch *b; + char *sp; + char *author = NULL; + char *committer = NULL; + struct hash_list *merge_list = NULL; + unsigned int merge_count; + + /* Obtain the branch name from the rest of our command */ + sp = strchr(command_buf.buf, ' ') + 1; + b = lookup_branch(sp); + if (!b) + b = new_branch(sp); + + read_next_command(); + parse_mark(); + if (!prefixcmp(command_buf.buf, "author ")) { + author = parse_ident(command_buf.buf + 7); + read_next_command(); + } + if (!prefixcmp(command_buf.buf, "committer ")) { + committer = parse_ident(command_buf.buf + 10); + read_next_command(); + } + if (!committer) + die("Expected committer but didn't get one"); + parse_data(&msg); + read_next_command(); + parse_from(b); + merge_list = parse_merge(&merge_count); + + /* ensure the branch is active/loaded */ + if (!b->branch_tree.tree || !max_active_branches) { + unload_one_branch(); + load_branch(b); + } + + /* file_change* */ + while (command_buf.len > 0) { + if (!prefixcmp(command_buf.buf, "M ")) + file_change_m(b); + else if (!prefixcmp(command_buf.buf, "D ")) + file_change_d(b); + else if (!prefixcmp(command_buf.buf, "R ")) + file_change_cr(b, 1); + else if (!prefixcmp(command_buf.buf, "C ")) + file_change_cr(b, 0); + else if (!strcmp("deleteall", command_buf.buf)) + file_change_deleteall(b); + else { + unread_command_buf = 1; + break; + } + if (read_next_command() == EOF) + break; + } + + /* build the tree and the commit */ + store_tree(&b->branch_tree); + hashcpy(b->branch_tree.versions[0].sha1, + b->branch_tree.versions[1].sha1); + + strbuf_reset(&new_data); + strbuf_addf(&new_data, "tree %s\n", + sha1_to_hex(b->branch_tree.versions[1].sha1)); + if (!is_null_sha1(b->sha1)) + strbuf_addf(&new_data, "parent %s\n", sha1_to_hex(b->sha1)); + while (merge_list) { + struct hash_list *next = merge_list->next; + strbuf_addf(&new_data, "parent %s\n", sha1_to_hex(merge_list->sha1)); + free(merge_list); + merge_list = next; + } + strbuf_addf(&new_data, + "author %s\n" + "committer %s\n" + "\n", + author ? author : committer, committer); + strbuf_addbuf(&new_data, &msg); + free(author); + free(committer); + + if (!store_object(OBJ_COMMIT, &new_data, NULL, b->sha1, next_mark)) + b->pack_id = pack_id; + b->last_commit = object_count_by_type[OBJ_COMMIT]; +} + +static void parse_new_tag(void) +{ + static struct strbuf msg = STRBUF_INIT; + char *sp; + const char *from; + char *tagger; + struct branch *s; + struct tag *t; + uintmax_t from_mark = 0; + unsigned char sha1[20]; + + /* Obtain the new tag name from the rest of our command */ + sp = strchr(command_buf.buf, ' ') + 1; + t = pool_alloc(sizeof(struct tag)); + t->next_tag = NULL; + t->name = pool_strdup(sp); + if (last_tag) + last_tag->next_tag = t; + else + first_tag = t; + last_tag = t; + read_next_command(); + + /* from ... */ + if (prefixcmp(command_buf.buf, "from ")) + die("Expected from command, got %s", command_buf.buf); + from = strchr(command_buf.buf, ' ') + 1; + s = lookup_branch(from); + if (s) { + hashcpy(sha1, s->sha1); + } else if (*from == ':') { + struct object_entry *oe; + from_mark = strtoumax(from + 1, NULL, 10); + oe = find_mark(from_mark); + if (oe->type != OBJ_COMMIT) + die("Mark :%" PRIuMAX " not a commit", from_mark); + hashcpy(sha1, oe->sha1); + } else if (!get_sha1(from, sha1)) { + unsigned long size; + char *buf; + + buf = read_object_with_reference(sha1, + commit_type, &size, sha1); + if (!buf || size < 46) + die("Not a valid commit: %s", from); + free(buf); + } else + die("Invalid ref name or SHA1 expression: %s", from); + read_next_command(); + + /* tagger ... */ + if (!prefixcmp(command_buf.buf, "tagger ")) { + tagger = parse_ident(command_buf.buf + 7); + read_next_command(); + } else + tagger = NULL; + + /* tag payload/message */ + parse_data(&msg); + + /* build the tag object */ + strbuf_reset(&new_data); + + strbuf_addf(&new_data, + "object %s\n" + "type %s\n" + "tag %s\n", + sha1_to_hex(sha1), commit_type, t->name); + if (tagger) + strbuf_addf(&new_data, + "tagger %s\n", tagger); + strbuf_addch(&new_data, '\n'); + strbuf_addbuf(&new_data, &msg); + free(tagger); + + if (store_object(OBJ_TAG, &new_data, NULL, t->sha1, 0)) + t->pack_id = MAX_PACK_ID; + else + t->pack_id = pack_id; +} + +static void parse_reset_branch(void) +{ + struct branch *b; + char *sp; + + /* Obtain the branch name from the rest of our command */ + sp = strchr(command_buf.buf, ' ') + 1; + b = lookup_branch(sp); + if (b) { + hashclr(b->sha1); + hashclr(b->branch_tree.versions[0].sha1); + hashclr(b->branch_tree.versions[1].sha1); + if (b->branch_tree.tree) { + release_tree_content_recursive(b->branch_tree.tree); + b->branch_tree.tree = NULL; + } + } + else + b = new_branch(sp); + read_next_command(); + parse_from(b); + if (command_buf.len > 0) + unread_command_buf = 1; +} + +static void parse_checkpoint(void) +{ + if (object_count) { + cycle_packfile(); + dump_branches(); + dump_tags(); + dump_marks(); + } + skip_optional_lf(); +} + +static void parse_progress(void) +{ + fwrite(command_buf.buf, 1, command_buf.len, stdout); + fputc('\n', stdout); + fflush(stdout); + skip_optional_lf(); +} + +static void import_marks(const char *input_file) +{ + char line[512]; + FILE *f = fopen(input_file, "r"); + if (!f) + die_errno("cannot read '%s'", input_file); + while (fgets(line, sizeof(line), f)) { + uintmax_t mark; + char *end; + unsigned char sha1[20]; + struct object_entry *e; + + end = strchr(line, '\n'); + if (line[0] != ':' || !end) + die("corrupt mark line: %s", line); + *end = 0; + mark = strtoumax(line + 1, &end, 10); + if (!mark || end == line + 1 + || *end != ' ' || get_sha1(end + 1, sha1)) + die("corrupt mark line: %s", line); + e = find_object(sha1); + if (!e) { + enum object_type type = sha1_object_info(sha1, NULL); + if (type < 0) + die("object not found: %s", sha1_to_hex(sha1)); + e = insert_object(sha1); + e->type = type; + e->pack_id = MAX_PACK_ID; + e->offset = 1; /* just not zero! */ + } + insert_mark(mark, e); + } + fclose(f); +} + +static int git_pack_config(const char *k, const char *v, void *cb) +{ + if (!strcmp(k, "pack.depth")) { + max_depth = git_config_int(k, v); + if (max_depth > MAX_DEPTH) + max_depth = MAX_DEPTH; + return 0; + } + if (!strcmp(k, "pack.compression")) { + int level = git_config_int(k, v); + if (level == -1) + level = Z_DEFAULT_COMPRESSION; + else if (level < 0 || level > Z_BEST_COMPRESSION) + die("bad pack compression level %d", level); + pack_compression_level = level; + pack_compression_seen = 1; + return 0; + } + return git_default_config(k, v, cb); +} + +static const char fast_import_usage[] = +"git fast-import [--date-format=f] [--max-pack-size=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]"; + +int main(int argc, const char **argv) +{ + unsigned int i, show_stats = 1; + + git_extract_argv0_path(argv[0]); + + setup_git_directory(); + git_config(git_pack_config, NULL); + if (!pack_compression_seen && core_compression_seen) + pack_compression_level = core_compression_level; + + alloc_objects(object_entry_alloc); + strbuf_init(&command_buf, 0); + atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*)); + branch_table = xcalloc(branch_table_sz, sizeof(struct branch*)); + avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*)); + marks = pool_calloc(1, sizeof(struct mark_set)); + + for (i = 1; i < argc; i++) { + const char *a = argv[i]; + + if (*a != '-' || !strcmp(a, "--")) + break; + else if (!prefixcmp(a, "--date-format=")) { + const char *fmt = a + 14; + if (!strcmp(fmt, "raw")) + whenspec = WHENSPEC_RAW; + else if (!strcmp(fmt, "rfc2822")) + whenspec = WHENSPEC_RFC2822; + else if (!strcmp(fmt, "now")) + whenspec = WHENSPEC_NOW; + else + die("unknown --date-format argument %s", fmt); + } + else if (!prefixcmp(a, "--max-pack-size=")) + max_packsize = strtoumax(a + 16, NULL, 0) * 1024 * 1024; + else if (!prefixcmp(a, "--depth=")) { + max_depth = strtoul(a + 8, NULL, 0); + if (max_depth > MAX_DEPTH) + die("--depth cannot exceed %u", MAX_DEPTH); + } + else if (!prefixcmp(a, "--active-branches=")) + max_active_branches = strtoul(a + 18, NULL, 0); + else if (!prefixcmp(a, "--import-marks=")) + import_marks(a + 15); + else if (!prefixcmp(a, "--export-marks=")) + mark_file = a + 15; + else if (!prefixcmp(a, "--export-pack-edges=")) { + if (pack_edges) + fclose(pack_edges); + pack_edges = fopen(a + 20, "a"); + if (!pack_edges) + die_errno("Cannot open '%s'", a + 20); + } else if (!strcmp(a, "--force")) + force_update = 1; + else if (!strcmp(a, "--quiet")) + show_stats = 0; + else if (!strcmp(a, "--stats")) + show_stats = 1; + else + die("unknown option %s", a); + } + if (i != argc) + usage(fast_import_usage); + + rc_free = pool_alloc(cmd_save * sizeof(*rc_free)); + for (i = 0; i < (cmd_save - 1); i++) + rc_free[i].next = &rc_free[i + 1]; + rc_free[cmd_save - 1].next = NULL; + + prepare_packed_git(); + start_packfile(); + set_die_routine(die_nicely); + while (read_next_command() != EOF) { + if (!strcmp("blob", command_buf.buf)) + parse_new_blob(); + else if (!prefixcmp(command_buf.buf, "commit ")) + parse_new_commit(); + else if (!prefixcmp(command_buf.buf, "tag ")) + parse_new_tag(); + else if (!prefixcmp(command_buf.buf, "reset ")) + parse_reset_branch(); + else if (!strcmp("checkpoint", command_buf.buf)) + parse_checkpoint(); + else if (!prefixcmp(command_buf.buf, "progress ")) + parse_progress(); + else + die("Unsupported command: %s", command_buf.buf); + } + end_packfile(); + + dump_branches(); + dump_tags(); + unkeep_all_packs(); + dump_marks(); + + if (pack_edges) + fclose(pack_edges); + + if (show_stats) { + uintmax_t total_count = 0, duplicate_count = 0; + for (i = 0; i < ARRAY_SIZE(object_count_by_type); i++) + total_count += object_count_by_type[i]; + for (i = 0; i < ARRAY_SIZE(duplicate_count_by_type); i++) + duplicate_count += duplicate_count_by_type[i]; + + fprintf(stderr, "%s statistics:\n", argv[0]); + fprintf(stderr, "---------------------------------------------------------------------\n"); + fprintf(stderr, "Alloc'd objects: %10" PRIuMAX "\n", alloc_count); + fprintf(stderr, "Total objects: %10" PRIuMAX " (%10" PRIuMAX " duplicates )\n", total_count, duplicate_count); + fprintf(stderr, " blobs : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]); + fprintf(stderr, " trees : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]); + fprintf(stderr, " commits: %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]); + fprintf(stderr, " tags : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG]); + fprintf(stderr, "Total branches: %10lu (%10lu loads )\n", branch_count, branch_load_count); + fprintf(stderr, " marks: %10" PRIuMAX " (%10" PRIuMAX " unique )\n", (((uintmax_t)1) << marks->shift) * 1024, marks_set_count); + fprintf(stderr, " atoms: %10u\n", atom_cnt); + fprintf(stderr, "Memory total: %10" PRIuMAX " KiB\n", (total_allocd + alloc_count*sizeof(struct object_entry))/1024); + fprintf(stderr, " pools: %10lu KiB\n", (unsigned long)(total_allocd/1024)); + fprintf(stderr, " objects: %10" PRIuMAX " KiB\n", (alloc_count*sizeof(struct object_entry))/1024); + fprintf(stderr, "---------------------------------------------------------------------\n"); + pack_report(); + fprintf(stderr, "---------------------------------------------------------------------\n"); + fprintf(stderr, "\n"); + } + + return failure ? 1 : 0; +} diff --git a/revision.c b/revision.c index 9fc4e8d3818f29..d2f3beddfef4a0 100644 --- a/revision.c +++ b/revision.c @@ -28,7 +28,7 @@ char *path_name(const struct name_path *path, const char *name) } n = xmalloc(len); m = n + len - (nlen + 1); - strcpy(m, name); + memcpy(m, name, nlen + 1); for (p = path; p; p = p->up) { if (p->elem_len) { m -= p->elem_len + 1; diff --git a/revision.c.orig b/revision.c.orig new file mode 100644 index 00000000000000..9fc4e8d3818f29 --- /dev/null +++ b/revision.c.orig @@ -0,0 +1,1928 @@ +#include "cache.h" +#include "tag.h" +#include "blob.h" +#include "tree.h" +#include "commit.h" +#include "diff.h" +#include "refs.h" +#include "revision.h" +#include "graph.h" +#include "grep.h" +#include "reflog-walk.h" +#include "patch-ids.h" +#include "decorate.h" +#include "log-tree.h" + +volatile show_early_output_fn_t show_early_output; + +char *path_name(const struct name_path *path, const char *name) +{ + const struct name_path *p; + char *n, *m; + int nlen = strlen(name); + int len = nlen + 1; + + for (p = path; p; p = p->up) { + if (p->elem_len) + len += p->elem_len + 1; + } + n = xmalloc(len); + m = n + len - (nlen + 1); + strcpy(m, name); + for (p = path; p; p = p->up) { + if (p->elem_len) { + m -= p->elem_len + 1; + memcpy(m, p->elem, p->elem_len); + m[p->elem_len] = '/'; + } + } + return n; +} + +void add_object(struct object *obj, + struct object_array *p, + struct name_path *path, + const char *name) +{ + add_object_array(obj, path_name(path, name), p); +} + +static void mark_blob_uninteresting(struct blob *blob) +{ + if (!blob) + return; + if (blob->object.flags & UNINTERESTING) + return; + blob->object.flags |= UNINTERESTING; +} + +void mark_tree_uninteresting(struct tree *tree) +{ + struct tree_desc desc; + struct name_entry entry; + struct object *obj = &tree->object; + + if (!tree) + return; + if (obj->flags & UNINTERESTING) + return; + obj->flags |= UNINTERESTING; + if (!has_sha1_file(obj->sha1)) + return; + if (parse_tree(tree) < 0) + die("bad tree %s", sha1_to_hex(obj->sha1)); + + init_tree_desc(&desc, tree->buffer, tree->size); + while (tree_entry(&desc, &entry)) { + switch (object_type(entry.mode)) { + case OBJ_TREE: + mark_tree_uninteresting(lookup_tree(entry.sha1)); + break; + case OBJ_BLOB: + mark_blob_uninteresting(lookup_blob(entry.sha1)); + break; + default: + /* Subproject commit - not in this repository */ + break; + } + } + + /* + * We don't care about the tree any more + * after it has been marked uninteresting. + */ + free(tree->buffer); + tree->buffer = NULL; +} + +void mark_parents_uninteresting(struct commit *commit) +{ + struct commit_list *parents = commit->parents; + + while (parents) { + struct commit *commit = parents->item; + if (!(commit->object.flags & UNINTERESTING)) { + commit->object.flags |= UNINTERESTING; + + /* + * Normally we haven't parsed the parent + * yet, so we won't have a parent of a parent + * here. However, it may turn out that we've + * reached this commit some other way (where it + * wasn't uninteresting), in which case we need + * to mark its parents recursively too.. + */ + if (commit->parents) + mark_parents_uninteresting(commit); + } + + /* + * A missing commit is ok iff its parent is marked + * uninteresting. + * + * We just mark such a thing parsed, so that when + * it is popped next time around, we won't be trying + * to parse it and get an error. + */ + if (!has_sha1_file(commit->object.sha1)) + commit->object.parsed = 1; + parents = parents->next; + } +} + +static void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode) +{ + if (revs->no_walk && (obj->flags & UNINTERESTING)) + revs->no_walk = 0; + if (revs->reflog_info && obj->type == OBJ_COMMIT && + add_reflog_for_walk(revs->reflog_info, + (struct commit *)obj, name)) + return; + add_object_array_with_mode(obj, name, &revs->pending, mode); +} + +void add_pending_object(struct rev_info *revs, struct object *obj, const char *name) +{ + add_pending_object_with_mode(revs, obj, name, S_IFINVALID); +} + +void add_head_to_pending(struct rev_info *revs) +{ + unsigned char sha1[20]; + struct object *obj; + if (get_sha1("HEAD", sha1)) + return; + obj = parse_object(sha1); + if (!obj) + return; + add_pending_object(revs, obj, "HEAD"); +} + +static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags) +{ + struct object *object; + + object = parse_object(sha1); + if (!object) + die("bad object %s", name); + object->flags |= flags; + return object; +} + +static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name) +{ + unsigned long flags = object->flags; + + /* + * Tag object? Look what it points to.. + */ + while (object->type == OBJ_TAG) { + struct tag *tag = (struct tag *) object; + if (revs->tag_objects && !(flags & UNINTERESTING)) + add_pending_object(revs, object, tag->tag); + if (!tag->tagged) + die("bad tag"); + object = parse_object(tag->tagged->sha1); + if (!object) { + if (flags & UNINTERESTING) + return NULL; + die("bad object %s", sha1_to_hex(tag->tagged->sha1)); + } + } + + /* + * Commit object? Just return it, we'll do all the complex + * reachability crud. + */ + if (object->type == OBJ_COMMIT) { + struct commit *commit = (struct commit *)object; + if (parse_commit(commit) < 0) + die("unable to parse commit %s", name); + if (flags & UNINTERESTING) { + commit->object.flags |= UNINTERESTING; + mark_parents_uninteresting(commit); + revs->limited = 1; + } + if (revs->show_source && !commit->util) + commit->util = (void *) name; + return commit; + } + + /* + * Tree object? Either mark it uninteresting, or add it + * to the list of objects to look at later.. + */ + if (object->type == OBJ_TREE) { + struct tree *tree = (struct tree *)object; + if (!revs->tree_objects) + return NULL; + if (flags & UNINTERESTING) { + mark_tree_uninteresting(tree); + return NULL; + } + add_pending_object(revs, object, ""); + return NULL; + } + + /* + * Blob object? You know the drill by now.. + */ + if (object->type == OBJ_BLOB) { + struct blob *blob = (struct blob *)object; + if (!revs->blob_objects) + return NULL; + if (flags & UNINTERESTING) { + mark_blob_uninteresting(blob); + return NULL; + } + add_pending_object(revs, object, ""); + return NULL; + } + die("%s is unknown object", name); +} + +static int everybody_uninteresting(struct commit_list *orig) +{ + struct commit_list *list = orig; + while (list) { + struct commit *commit = list->item; + list = list->next; + if (commit->object.flags & UNINTERESTING) + continue; + return 0; + } + return 1; +} + +/* + * The goal is to get REV_TREE_NEW as the result only if the + * diff consists of all '+' (and no other changes), REV_TREE_OLD + * if the whole diff is removal of old data, and otherwise + * REV_TREE_DIFFERENT (of course if the trees are the same we + * want REV_TREE_SAME). + * That means that once we get to REV_TREE_DIFFERENT, we do not + * have to look any further. + */ +static int tree_difference = REV_TREE_SAME; + +static void file_add_remove(struct diff_options *options, + int addremove, unsigned mode, + const unsigned char *sha1, + const char *fullpath) +{ + int diff = addremove == '+' ? REV_TREE_NEW : REV_TREE_OLD; + + tree_difference |= diff; + if (tree_difference == REV_TREE_DIFFERENT) + DIFF_OPT_SET(options, HAS_CHANGES); +} + +static void file_change(struct diff_options *options, + unsigned old_mode, unsigned new_mode, + const unsigned char *old_sha1, + const unsigned char *new_sha1, + const char *fullpath) +{ + tree_difference = REV_TREE_DIFFERENT; + DIFF_OPT_SET(options, HAS_CHANGES); +} + +static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct commit *commit) +{ + struct tree *t1 = parent->tree; + struct tree *t2 = commit->tree; + + if (!t1) + return REV_TREE_NEW; + if (!t2) + return REV_TREE_OLD; + + if (revs->simplify_by_decoration) { + /* + * If we are simplifying by decoration, then the commit + * is worth showing if it has a tag pointing at it. + */ + if (lookup_decoration(&name_decoration, &commit->object)) + return REV_TREE_DIFFERENT; + /* + * A commit that is not pointed by a tag is uninteresting + * if we are not limited by path. This means that you will + * see the usual "commits that touch the paths" plus any + * tagged commit by specifying both --simplify-by-decoration + * and pathspec. + */ + if (!revs->prune_data) + return REV_TREE_SAME; + } + + tree_difference = REV_TREE_SAME; + DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES); + if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", + &revs->pruning) < 0) + return REV_TREE_DIFFERENT; + return tree_difference; +} + +static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit) +{ + int retval; + void *tree; + unsigned long size; + struct tree_desc empty, real; + struct tree *t1 = commit->tree; + + if (!t1) + return 0; + + tree = read_object_with_reference(t1->object.sha1, tree_type, &size, NULL); + if (!tree) + return 0; + init_tree_desc(&real, tree, size); + init_tree_desc(&empty, "", 0); + + tree_difference = REV_TREE_SAME; + DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES); + retval = diff_tree(&empty, &real, "", &revs->pruning); + free(tree); + + return retval >= 0 && (tree_difference == REV_TREE_SAME); +} + +static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) +{ + struct commit_list **pp, *parent; + int tree_changed = 0, tree_same = 0; + + /* + * If we don't do pruning, everything is interesting + */ + if (!revs->prune) + return; + + if (!commit->tree) + return; + + if (!commit->parents) { + if (rev_same_tree_as_empty(revs, commit)) + commit->object.flags |= TREESAME; + return; + } + + /* + * Normal non-merge commit? If we don't want to make the + * history dense, we consider it always to be a change.. + */ + if (!revs->dense && !commit->parents->next) + return; + + pp = &commit->parents; + while ((parent = *pp) != NULL) { + struct commit *p = parent->item; + + if (parse_commit(p) < 0) + die("cannot simplify commit %s (because of %s)", + sha1_to_hex(commit->object.sha1), + sha1_to_hex(p->object.sha1)); + switch (rev_compare_tree(revs, p, commit)) { + case REV_TREE_SAME: + tree_same = 1; + if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) { + /* Even if a merge with an uninteresting + * side branch brought the entire change + * we are interested in, we do not want + * to lose the other branches of this + * merge, so we just keep going. + */ + pp = &parent->next; + continue; + } + parent->next = NULL; + commit->parents = parent; + commit->object.flags |= TREESAME; + return; + + case REV_TREE_NEW: + if (revs->remove_empty_trees && + rev_same_tree_as_empty(revs, p)) { + /* We are adding all the specified + * paths from this parent, so the + * history beyond this parent is not + * interesting. Remove its parents + * (they are grandparents for us). + * IOW, we pretend this parent is a + * "root" commit. + */ + if (parse_commit(p) < 0) + die("cannot simplify commit %s (invalid %s)", + sha1_to_hex(commit->object.sha1), + sha1_to_hex(p->object.sha1)); + p->parents = NULL; + } + /* fallthrough */ + case REV_TREE_OLD: + case REV_TREE_DIFFERENT: + tree_changed = 1; + pp = &parent->next; + continue; + } + die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1)); + } + if (tree_changed && !tree_same) + return; + commit->object.flags |= TREESAME; +} + +static void insert_by_date_cached(struct commit *p, struct commit_list **head, + struct commit_list *cached_base, struct commit_list **cache) +{ + struct commit_list *new_entry; + + if (cached_base && p->date < cached_base->item->date) + new_entry = insert_by_date(p, &cached_base->next); + else + new_entry = insert_by_date(p, head); + + if (cache && (!*cache || p->date < (*cache)->item->date)) + *cache = new_entry; +} + +static int add_parents_to_list(struct rev_info *revs, struct commit *commit, + struct commit_list **list, struct commit_list **cache_ptr) +{ + struct commit_list *parent = commit->parents; + unsigned left_flag; + struct commit_list *cached_base = cache_ptr ? *cache_ptr : NULL; + + if (commit->object.flags & ADDED) + return 0; + commit->object.flags |= ADDED; + + /* + * If the commit is uninteresting, don't try to + * prune parents - we want the maximal uninteresting + * set. + * + * Normally we haven't parsed the parent + * yet, so we won't have a parent of a parent + * here. However, it may turn out that we've + * reached this commit some other way (where it + * wasn't uninteresting), in which case we need + * to mark its parents recursively too.. + */ + if (commit->object.flags & UNINTERESTING) { + while (parent) { + struct commit *p = parent->item; + parent = parent->next; + if (p) + p->object.flags |= UNINTERESTING; + if (parse_commit(p) < 0) + continue; + if (p->parents) + mark_parents_uninteresting(p); + if (p->object.flags & SEEN) + continue; + p->object.flags |= SEEN; + insert_by_date_cached(p, list, cached_base, cache_ptr); + } + return 0; + } + + /* + * Ok, the commit wasn't uninteresting. Try to + * simplify the commit history and find the parent + * that has no differences in the path set if one exists. + */ + try_to_simplify_commit(revs, commit); + + if (revs->no_walk) + return 0; + + left_flag = (commit->object.flags & SYMMETRIC_LEFT); + + for (parent = commit->parents; parent; parent = parent->next) { + struct commit *p = parent->item; + + if (parse_commit(p) < 0) + return -1; + if (revs->show_source && !p->util) + p->util = commit->util; + p->object.flags |= left_flag; + if (!(p->object.flags & SEEN)) { + p->object.flags |= SEEN; + insert_by_date_cached(p, list, cached_base, cache_ptr); + } + if (revs->first_parent_only) + break; + } + return 0; +} + +static void cherry_pick_list(struct commit_list *list, struct rev_info *revs) +{ + struct commit_list *p; + int left_count = 0, right_count = 0; + int left_first; + struct patch_ids ids; + + /* First count the commits on the left and on the right */ + for (p = list; p; p = p->next) { + struct commit *commit = p->item; + unsigned flags = commit->object.flags; + if (flags & BOUNDARY) + ; + else if (flags & SYMMETRIC_LEFT) + left_count++; + else + right_count++; + } + + left_first = left_count < right_count; + init_patch_ids(&ids); + if (revs->diffopt.nr_paths) { + ids.diffopts.nr_paths = revs->diffopt.nr_paths; + ids.diffopts.paths = revs->diffopt.paths; + ids.diffopts.pathlens = revs->diffopt.pathlens; + } + + /* Compute patch-ids for one side */ + for (p = list; p; p = p->next) { + struct commit *commit = p->item; + unsigned flags = commit->object.flags; + + if (flags & BOUNDARY) + continue; + /* + * If we have fewer left, left_first is set and we omit + * commits on the right branch in this loop. If we have + * fewer right, we skip the left ones. + */ + if (left_first != !!(flags & SYMMETRIC_LEFT)) + continue; + commit->util = add_commit_patch_id(commit, &ids); + } + + /* Check the other side */ + for (p = list; p; p = p->next) { + struct commit *commit = p->item; + struct patch_id *id; + unsigned flags = commit->object.flags; + + if (flags & BOUNDARY) + continue; + /* + * If we have fewer left, left_first is set and we omit + * commits on the left branch in this loop. + */ + if (left_first == !!(flags & SYMMETRIC_LEFT)) + continue; + + /* + * Have we seen the same patch id? + */ + id = has_commit_patch_id(commit, &ids); + if (!id) + continue; + id->seen = 1; + commit->object.flags |= SHOWN; + } + + /* Now check the original side for seen ones */ + for (p = list; p; p = p->next) { + struct commit *commit = p->item; + struct patch_id *ent; + + ent = commit->util; + if (!ent) + continue; + if (ent->seen) + commit->object.flags |= SHOWN; + commit->util = NULL; + } + + free_patch_ids(&ids); +} + +/* How many extra uninteresting commits we want to see.. */ +#define SLOP 5 + +static int still_interesting(struct commit_list *src, unsigned long date, int slop) +{ + /* + * No source list at all? We're definitely done.. + */ + if (!src) + return 0; + + /* + * Does the destination list contain entries with a date + * before the source list? Definitely _not_ done. + */ + if (date < src->item->date) + return SLOP; + + /* + * Does the source list still have interesting commits in + * it? Definitely not done.. + */ + if (!everybody_uninteresting(src)) + return SLOP; + + /* Ok, we're closing in.. */ + return slop-1; +} + +static int limit_list(struct rev_info *revs) +{ + int slop = SLOP; + unsigned long date = ~0ul; + struct commit_list *list = revs->commits; + struct commit_list *newlist = NULL; + struct commit_list **p = &newlist; + + while (list) { + struct commit_list *entry = list; + struct commit *commit = list->item; + struct object *obj = &commit->object; + show_early_output_fn_t show; + + list = list->next; + free(entry); + + if (revs->max_age != -1 && (commit->date < revs->max_age)) + obj->flags |= UNINTERESTING; + if (add_parents_to_list(revs, commit, &list, NULL) < 0) + return -1; + if (obj->flags & UNINTERESTING) { + mark_parents_uninteresting(commit); + if (revs->show_all) + p = &commit_list_insert(commit, p)->next; + slop = still_interesting(list, date, slop); + if (slop) + continue; + /* If showing all, add the whole pending list to the end */ + if (revs->show_all) + *p = list; + break; + } + if (revs->min_age != -1 && (commit->date > revs->min_age)) + continue; + date = commit->date; + p = &commit_list_insert(commit, p)->next; + + show = show_early_output; + if (!show) + continue; + + show(revs, newlist); + show_early_output = NULL; + } + if (revs->cherry_pick) + cherry_pick_list(newlist, revs); + + revs->commits = newlist; + return 0; +} + +struct all_refs_cb { + int all_flags; + int warned_bad_reflog; + struct rev_info *all_revs; + const char *name_for_errormsg; +}; + +static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) +{ + struct all_refs_cb *cb = cb_data; + struct object *object = get_reference(cb->all_revs, path, sha1, + cb->all_flags); + add_pending_object(cb->all_revs, object, path); + return 0; +} + +static void handle_refs(struct rev_info *revs, unsigned flags, + int (*for_each)(each_ref_fn, void *)) +{ + struct all_refs_cb cb; + cb.all_revs = revs; + cb.all_flags = flags; + for_each(handle_one_ref, &cb); +} + +static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data) +{ + struct all_refs_cb *cb = cb_data; + if (!is_null_sha1(sha1)) { + struct object *o = parse_object(sha1); + if (o) { + o->flags |= cb->all_flags; + add_pending_object(cb->all_revs, o, ""); + } + else if (!cb->warned_bad_reflog) { + warning("reflog of '%s' references pruned commits", + cb->name_for_errormsg); + cb->warned_bad_reflog = 1; + } + } +} + +static int handle_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, int tz, + const char *message, void *cb_data) +{ + handle_one_reflog_commit(osha1, cb_data); + handle_one_reflog_commit(nsha1, cb_data); + return 0; +} + +static int handle_one_reflog(const char *path, const unsigned char *sha1, int flag, void *cb_data) +{ + struct all_refs_cb *cb = cb_data; + cb->warned_bad_reflog = 0; + cb->name_for_errormsg = path; + for_each_reflog_ent(path, handle_one_reflog_ent, cb_data); + return 0; +} + +static void handle_reflog(struct rev_info *revs, unsigned flags) +{ + struct all_refs_cb cb; + cb.all_revs = revs; + cb.all_flags = flags; + for_each_reflog(handle_one_reflog, &cb); +} + +static int add_parents_only(struct rev_info *revs, const char *arg, int flags) +{ + unsigned char sha1[20]; + struct object *it; + struct commit *commit; + struct commit_list *parents; + + if (*arg == '^') { + flags ^= UNINTERESTING; + arg++; + } + if (get_sha1(arg, sha1)) + return 0; + while (1) { + it = get_reference(revs, arg, sha1, 0); + if (it->type != OBJ_TAG) + break; + if (!((struct tag*)it)->tagged) + return 0; + hashcpy(sha1, ((struct tag*)it)->tagged->sha1); + } + if (it->type != OBJ_COMMIT) + return 0; + commit = (struct commit *)it; + for (parents = commit->parents; parents; parents = parents->next) { + it = &parents->item->object; + it->flags |= flags; + add_pending_object(revs, it, arg); + } + return 1; +} + +void init_revisions(struct rev_info *revs, const char *prefix) +{ + memset(revs, 0, sizeof(*revs)); + + revs->abbrev = DEFAULT_ABBREV; + revs->ignore_merges = 1; + revs->simplify_history = 1; + DIFF_OPT_SET(&revs->pruning, RECURSIVE); + DIFF_OPT_SET(&revs->pruning, QUIET); + revs->pruning.add_remove = file_add_remove; + revs->pruning.change = file_change; + revs->lifo = 1; + revs->dense = 1; + revs->prefix = prefix; + revs->max_age = -1; + revs->min_age = -1; + revs->skip_count = -1; + revs->max_count = -1; + + revs->commit_format = CMIT_FMT_DEFAULT; + + revs->grep_filter.status_only = 1; + revs->grep_filter.pattern_tail = &(revs->grep_filter.pattern_list); + revs->grep_filter.regflags = REG_NEWLINE; + + diff_setup(&revs->diffopt); + if (prefix && !revs->diffopt.prefix) { + revs->diffopt.prefix = prefix; + revs->diffopt.prefix_length = strlen(prefix); + } +} + +static void add_pending_commit_list(struct rev_info *revs, + struct commit_list *commit_list, + unsigned int flags) +{ + while (commit_list) { + struct object *object = &commit_list->item->object; + object->flags |= flags; + add_pending_object(revs, object, sha1_to_hex(object->sha1)); + commit_list = commit_list->next; + } +} + +static void prepare_show_merge(struct rev_info *revs) +{ + struct commit_list *bases; + struct commit *head, *other; + unsigned char sha1[20]; + const char **prune = NULL; + int i, prune_num = 1; /* counting terminating NULL */ + + if (get_sha1("HEAD", sha1) || !(head = lookup_commit(sha1))) + die("--merge without HEAD?"); + if (get_sha1("MERGE_HEAD", sha1) || !(other = lookup_commit(sha1))) + die("--merge without MERGE_HEAD?"); + add_pending_object(revs, &head->object, "HEAD"); + add_pending_object(revs, &other->object, "MERGE_HEAD"); + bases = get_merge_bases(head, other, 1); + add_pending_commit_list(revs, bases, UNINTERESTING); + free_commit_list(bases); + head->object.flags |= SYMMETRIC_LEFT; + + if (!active_nr) + read_cache(); + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (!ce_stage(ce)) + continue; + if (ce_path_match(ce, revs->prune_data)) { + prune_num++; + prune = xrealloc(prune, sizeof(*prune) * prune_num); + prune[prune_num-2] = ce->name; + prune[prune_num-1] = NULL; + } + while ((i+1 < active_nr) && + ce_same_name(ce, active_cache[i+1])) + i++; + } + revs->prune_data = prune; + revs->limited = 1; +} + +int handle_revision_arg(const char *arg, struct rev_info *revs, + int flags, + int cant_be_filename) +{ + unsigned mode; + char *dotdot; + struct object *object; + unsigned char sha1[20]; + int local_flags; + + dotdot = strstr(arg, ".."); + if (dotdot) { + unsigned char from_sha1[20]; + const char *next = dotdot + 2; + const char *this = arg; + int symmetric = *next == '.'; + unsigned int flags_exclude = flags ^ UNINTERESTING; + + *dotdot = 0; + next += symmetric; + + if (!*next) + next = "HEAD"; + if (dotdot == arg) + this = "HEAD"; + if (!get_sha1(this, from_sha1) && + !get_sha1(next, sha1)) { + struct commit *a, *b; + struct commit_list *exclude; + + a = lookup_commit_reference(from_sha1); + b = lookup_commit_reference(sha1); + if (!a || !b) { + die(symmetric ? + "Invalid symmetric difference expression %s...%s" : + "Invalid revision range %s..%s", + arg, next); + } + + if (!cant_be_filename) { + *dotdot = '.'; + verify_non_filename(revs->prefix, arg); + } + + if (symmetric) { + exclude = get_merge_bases(a, b, 1); + add_pending_commit_list(revs, exclude, + flags_exclude); + free_commit_list(exclude); + a->object.flags |= flags | SYMMETRIC_LEFT; + } else + a->object.flags |= flags_exclude; + b->object.flags |= flags; + add_pending_object(revs, &a->object, this); + add_pending_object(revs, &b->object, next); + return 0; + } + *dotdot = '.'; + } + dotdot = strstr(arg, "^@"); + if (dotdot && !dotdot[2]) { + *dotdot = 0; + if (add_parents_only(revs, arg, flags)) + return 0; + *dotdot = '^'; + } + dotdot = strstr(arg, "^!"); + if (dotdot && !dotdot[2]) { + *dotdot = 0; + if (!add_parents_only(revs, arg, flags ^ UNINTERESTING)) + *dotdot = '^'; + } + + local_flags = 0; + if (*arg == '^') { + local_flags = UNINTERESTING; + arg++; + } + if (get_sha1_with_mode(arg, sha1, &mode)) + return -1; + if (!cant_be_filename) + verify_non_filename(revs->prefix, arg); + object = get_reference(revs, arg, sha1, flags ^ local_flags); + add_pending_object_with_mode(revs, object, arg, mode); + return 0; +} + +void read_revisions_from_stdin(struct rev_info *revs) +{ + char line[1000]; + + while (fgets(line, sizeof(line), stdin) != NULL) { + int len = strlen(line); + if (len && line[len - 1] == '\n') + line[--len] = '\0'; + if (!len) + break; + if (line[0] == '-') + die("options not supported in --stdin mode"); + if (handle_revision_arg(line, revs, 0, 1)) + die("bad revision '%s'", line); + } +} + +static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what) +{ + append_grep_pattern(&revs->grep_filter, ptn, "command line", 0, what); +} + +static void add_header_grep(struct rev_info *revs, enum grep_header_field field, const char *pattern) +{ + append_header_grep_pattern(&revs->grep_filter, field, pattern); +} + +static void add_message_grep(struct rev_info *revs, const char *pattern) +{ + add_grep(revs, pattern, GREP_PATTERN_BODY); +} + +static int handle_revision_opt(struct rev_info *revs, int argc, const char **argv, + int *unkc, const char **unkv) +{ + const char *arg = argv[0]; + + /* pseudo revision arguments */ + if (!strcmp(arg, "--all") || !strcmp(arg, "--branches") || + !strcmp(arg, "--tags") || !strcmp(arg, "--remotes") || + !strcmp(arg, "--reflog") || !strcmp(arg, "--not") || + !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk")) + { + unkv[(*unkc)++] = arg; + return 1; + } + + if (!prefixcmp(arg, "--max-count=")) { + revs->max_count = atoi(arg + 12); + } else if (!prefixcmp(arg, "--skip=")) { + revs->skip_count = atoi(arg + 7); + } else if ((*arg == '-') && isdigit(arg[1])) { + /* accept -, like traditional "head" */ + revs->max_count = atoi(arg + 1); + } else if (!strcmp(arg, "-n")) { + if (argc <= 1) + return error("-n requires an argument"); + revs->max_count = atoi(argv[1]); + return 2; + } else if (!prefixcmp(arg, "-n")) { + revs->max_count = atoi(arg + 2); + } else if (!prefixcmp(arg, "--max-age=")) { + revs->max_age = atoi(arg + 10); + } else if (!prefixcmp(arg, "--since=")) { + revs->max_age = approxidate(arg + 8); + } else if (!prefixcmp(arg, "--after=")) { + revs->max_age = approxidate(arg + 8); + } else if (!prefixcmp(arg, "--min-age=")) { + revs->min_age = atoi(arg + 10); + } else if (!prefixcmp(arg, "--before=")) { + revs->min_age = approxidate(arg + 9); + } else if (!prefixcmp(arg, "--until=")) { + revs->min_age = approxidate(arg + 8); + } else if (!strcmp(arg, "--first-parent")) { + revs->first_parent_only = 1; + } else if (!strcmp(arg, "-g") || !strcmp(arg, "--walk-reflogs")) { + init_reflog_walk(&revs->reflog_info); + } else if (!strcmp(arg, "--default")) { + if (argc <= 1) + return error("bad --default argument"); + revs->def = argv[1]; + return 2; + } else if (!strcmp(arg, "--merge")) { + revs->show_merge = 1; + } else if (!strcmp(arg, "--topo-order")) { + revs->lifo = 1; + revs->topo_order = 1; + } else if (!strcmp(arg, "--simplify-merges")) { + revs->simplify_merges = 1; + revs->rewrite_parents = 1; + revs->simplify_history = 0; + revs->limited = 1; + } else if (!strcmp(arg, "--simplify-by-decoration")) { + revs->simplify_merges = 1; + revs->rewrite_parents = 1; + revs->simplify_history = 0; + revs->simplify_by_decoration = 1; + revs->limited = 1; + revs->prune = 1; + load_ref_decorations(DECORATE_SHORT_REFS); + } else if (!strcmp(arg, "--date-order")) { + revs->lifo = 0; + revs->topo_order = 1; + } else if (!prefixcmp(arg, "--early-output")) { + int count = 100; + switch (arg[14]) { + case '=': + count = atoi(arg+15); + /* Fallthrough */ + case 0: + revs->topo_order = 1; + revs->early_output = count; + } + } else if (!strcmp(arg, "--parents")) { + revs->rewrite_parents = 1; + revs->print_parents = 1; + } else if (!strcmp(arg, "--dense")) { + revs->dense = 1; + } else if (!strcmp(arg, "--sparse")) { + revs->dense = 0; + } else if (!strcmp(arg, "--show-all")) { + revs->show_all = 1; + } else if (!strcmp(arg, "--remove-empty")) { + revs->remove_empty_trees = 1; + } else if (!strcmp(arg, "--merges")) { + revs->merges_only = 1; + } else if (!strcmp(arg, "--no-merges")) { + revs->no_merges = 1; + } else if (!strcmp(arg, "--boundary")) { + revs->boundary = 1; + } else if (!strcmp(arg, "--left-right")) { + revs->left_right = 1; + } else if (!strcmp(arg, "--cherry-pick")) { + revs->cherry_pick = 1; + revs->limited = 1; + } else if (!strcmp(arg, "--objects")) { + revs->tag_objects = 1; + revs->tree_objects = 1; + revs->blob_objects = 1; + } else if (!strcmp(arg, "--objects-edge")) { + revs->tag_objects = 1; + revs->tree_objects = 1; + revs->blob_objects = 1; + revs->edge_hint = 1; + } else if (!strcmp(arg, "--unpacked")) { + revs->unpacked = 1; + } else if (!prefixcmp(arg, "--unpacked=")) { + die("--unpacked= no longer supported."); + } else if (!strcmp(arg, "-r")) { + revs->diff = 1; + DIFF_OPT_SET(&revs->diffopt, RECURSIVE); + } else if (!strcmp(arg, "-t")) { + revs->diff = 1; + DIFF_OPT_SET(&revs->diffopt, RECURSIVE); + DIFF_OPT_SET(&revs->diffopt, TREE_IN_RECURSIVE); + } else if (!strcmp(arg, "-m")) { + revs->ignore_merges = 0; + } else if (!strcmp(arg, "-c")) { + revs->diff = 1; + revs->dense_combined_merges = 0; + revs->combine_merges = 1; + } else if (!strcmp(arg, "--cc")) { + revs->diff = 1; + revs->dense_combined_merges = 1; + revs->combine_merges = 1; + } else if (!strcmp(arg, "-v")) { + revs->verbose_header = 1; + } else if (!strcmp(arg, "--pretty")) { + revs->verbose_header = 1; + get_commit_format(arg+8, revs); + } else if (!prefixcmp(arg, "--pretty=") || !prefixcmp(arg, "--format=")) { + revs->verbose_header = 1; + get_commit_format(arg+9, revs); + } else if (!strcmp(arg, "--oneline")) { + revs->verbose_header = 1; + get_commit_format("oneline", revs); + revs->abbrev_commit = 1; + } else if (!strcmp(arg, "--graph")) { + revs->topo_order = 1; + revs->rewrite_parents = 1; + revs->graph = graph_init(revs); + } else if (!strcmp(arg, "--root")) { + revs->show_root_diff = 1; + } else if (!strcmp(arg, "--no-commit-id")) { + revs->no_commit_id = 1; + } else if (!strcmp(arg, "--always")) { + revs->always_show_header = 1; + } else if (!strcmp(arg, "--no-abbrev")) { + revs->abbrev = 0; + } else if (!strcmp(arg, "--abbrev")) { + revs->abbrev = DEFAULT_ABBREV; + } else if (!prefixcmp(arg, "--abbrev=")) { + revs->abbrev = strtoul(arg + 9, NULL, 10); + if (revs->abbrev < MINIMUM_ABBREV) + revs->abbrev = MINIMUM_ABBREV; + else if (revs->abbrev > 40) + revs->abbrev = 40; + } else if (!strcmp(arg, "--abbrev-commit")) { + revs->abbrev_commit = 1; + } else if (!strcmp(arg, "--full-diff")) { + revs->diff = 1; + revs->full_diff = 1; + } else if (!strcmp(arg, "--full-history")) { + revs->simplify_history = 0; + } else if (!strcmp(arg, "--relative-date")) { + revs->date_mode = DATE_RELATIVE; + revs->date_mode_explicit = 1; + } else if (!strncmp(arg, "--date=", 7)) { + revs->date_mode = parse_date_format(arg + 7); + revs->date_mode_explicit = 1; + } else if (!strcmp(arg, "--log-size")) { + revs->show_log_size = 1; + } + /* + * Grepping the commit log + */ + else if (!prefixcmp(arg, "--author=")) { + add_header_grep(revs, GREP_HEADER_AUTHOR, arg+9); + } else if (!prefixcmp(arg, "--committer=")) { + add_header_grep(revs, GREP_HEADER_COMMITTER, arg+12); + } else if (!prefixcmp(arg, "--grep=")) { + add_message_grep(revs, arg+7); + } else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) { + revs->grep_filter.regflags |= REG_EXTENDED; + } else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) { + revs->grep_filter.regflags |= REG_ICASE; + } else if (!strcmp(arg, "--fixed-strings") || !strcmp(arg, "-F")) { + revs->grep_filter.fixed = 1; + } else if (!strcmp(arg, "--all-match")) { + revs->grep_filter.all_match = 1; + } else if (!prefixcmp(arg, "--encoding=")) { + arg += 11; + if (strcmp(arg, "none")) + git_log_output_encoding = xstrdup(arg); + else + git_log_output_encoding = ""; + } else if (!strcmp(arg, "--reverse")) { + revs->reverse ^= 1; + } else if (!strcmp(arg, "--children")) { + revs->children.name = "children"; + revs->limited = 1; + } else { + int opts = diff_opt_parse(&revs->diffopt, argv, argc); + if (!opts) + unkv[(*unkc)++] = arg; + return opts; + } + + return 1; +} + +void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx, + const struct option *options, + const char * const usagestr[]) +{ + int n = handle_revision_opt(revs, ctx->argc, ctx->argv, + &ctx->cpidx, ctx->out); + if (n <= 0) { + error("unknown option `%s'", ctx->argv[0]); + usage_with_options(usagestr, options); + } + ctx->argv += n; + ctx->argc -= n; +} + +/* + * Parse revision information, filling in the "rev_info" structure, + * and removing the used arguments from the argument list. + * + * Returns the number of arguments left that weren't recognized + * (which are also moved to the head of the argument list) + */ +int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def) +{ + int i, flags, left, seen_dashdash; + + /* First, search for "--" */ + seen_dashdash = 0; + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (strcmp(arg, "--")) + continue; + argv[i] = NULL; + argc = i; + if (argv[i + 1]) + revs->prune_data = get_pathspec(revs->prefix, argv + i + 1); + seen_dashdash = 1; + break; + } + + /* Second, deal with arguments and options */ + flags = 0; + for (left = i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (*arg == '-') { + int opts; + + if (!strcmp(arg, "--all")) { + handle_refs(revs, flags, for_each_ref); + handle_refs(revs, flags, head_ref); + continue; + } + if (!strcmp(arg, "--branches")) { + handle_refs(revs, flags, for_each_branch_ref); + continue; + } + if (!strcmp(arg, "--tags")) { + handle_refs(revs, flags, for_each_tag_ref); + continue; + } + if (!strcmp(arg, "--remotes")) { + handle_refs(revs, flags, for_each_remote_ref); + continue; + } + if (!strcmp(arg, "--reflog")) { + handle_reflog(revs, flags); + continue; + } + if (!strcmp(arg, "--not")) { + flags ^= UNINTERESTING; + continue; + } + if (!strcmp(arg, "--no-walk")) { + revs->no_walk = 1; + continue; + } + if (!strcmp(arg, "--do-walk")) { + revs->no_walk = 0; + continue; + } + + opts = handle_revision_opt(revs, argc - i, argv + i, &left, argv); + if (opts > 0) { + i += opts - 1; + continue; + } + if (opts < 0) + exit(128); + continue; + } + + if (handle_revision_arg(arg, revs, flags, seen_dashdash)) { + int j; + if (seen_dashdash || *arg == '^') + die("bad revision '%s'", arg); + + /* If we didn't have a "--": + * (1) all filenames must exist; + * (2) all rev-args must not be interpretable + * as a valid filename. + * but the latter we have checked in the main loop. + */ + for (j = i; j < argc; j++) + verify_filename(revs->prefix, argv[j]); + + revs->prune_data = get_pathspec(revs->prefix, + argv + i); + break; + } + } + + if (revs->def == NULL) + revs->def = def; + if (revs->show_merge) + prepare_show_merge(revs); + if (revs->def && !revs->pending.nr) { + unsigned char sha1[20]; + struct object *object; + unsigned mode; + if (get_sha1_with_mode(revs->def, sha1, &mode)) + die("bad default revision '%s'", revs->def); + object = get_reference(revs, revs->def, sha1, 0); + add_pending_object_with_mode(revs, object, revs->def, mode); + } + + /* Did the user ask for any diff output? Run the diff! */ + if (revs->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) + revs->diff = 1; + + /* Pickaxe, diff-filter and rename following need diffs */ + if (revs->diffopt.pickaxe || + revs->diffopt.filter || + DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES)) + revs->diff = 1; + + if (revs->topo_order) + revs->limited = 1; + + if (revs->prune_data) { + diff_tree_setup_paths(revs->prune_data, &revs->pruning); + /* Can't prune commits with rename following: the paths change.. */ + if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES)) + revs->prune = 1; + if (!revs->full_diff) + diff_tree_setup_paths(revs->prune_data, &revs->diffopt); + } + if (revs->combine_merges) { + revs->ignore_merges = 0; + if (revs->dense_combined_merges && !revs->diffopt.output_format) + revs->diffopt.output_format = DIFF_FORMAT_PATCH; + } + revs->diffopt.abbrev = revs->abbrev; + if (diff_setup_done(&revs->diffopt) < 0) + die("diff_setup_done failed"); + + compile_grep_patterns(&revs->grep_filter); + + if (revs->reverse && revs->reflog_info) + die("cannot combine --reverse with --walk-reflogs"); + if (revs->rewrite_parents && revs->children.name) + die("cannot combine --parents and --children"); + + /* + * Limitations on the graph functionality + */ + if (revs->reverse && revs->graph) + die("cannot combine --reverse with --graph"); + + if (revs->reflog_info && revs->graph) + die("cannot combine --walk-reflogs with --graph"); + + return left; +} + +static void add_child(struct rev_info *revs, struct commit *parent, struct commit *child) +{ + struct commit_list *l = xcalloc(1, sizeof(*l)); + + l->item = child; + l->next = add_decoration(&revs->children, &parent->object, l); +} + +static int remove_duplicate_parents(struct commit *commit) +{ + struct commit_list **pp, *p; + int surviving_parents; + + /* Examine existing parents while marking ones we have seen... */ + pp = &commit->parents; + while ((p = *pp) != NULL) { + struct commit *parent = p->item; + if (parent->object.flags & TMP_MARK) { + *pp = p->next; + continue; + } + parent->object.flags |= TMP_MARK; + pp = &p->next; + } + /* count them while clearing the temporary mark */ + surviving_parents = 0; + for (p = commit->parents; p; p = p->next) { + p->item->object.flags &= ~TMP_MARK; + surviving_parents++; + } + return surviving_parents; +} + +struct merge_simplify_state { + struct commit *simplified; +}; + +static struct merge_simplify_state *locate_simplify_state(struct rev_info *revs, struct commit *commit) +{ + struct merge_simplify_state *st; + + st = lookup_decoration(&revs->merge_simplification, &commit->object); + if (!st) { + st = xcalloc(1, sizeof(*st)); + add_decoration(&revs->merge_simplification, &commit->object, st); + } + return st; +} + +static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail) +{ + struct commit_list *p; + struct merge_simplify_state *st, *pst; + int cnt; + + st = locate_simplify_state(revs, commit); + + /* + * Have we handled this one? + */ + if (st->simplified) + return tail; + + /* + * An UNINTERESTING commit simplifies to itself, so does a + * root commit. We do not rewrite parents of such commit + * anyway. + */ + if ((commit->object.flags & UNINTERESTING) || !commit->parents) { + st->simplified = commit; + return tail; + } + + /* + * Do we know what commit all of our parents should be rewritten to? + * Otherwise we are not ready to rewrite this one yet. + */ + for (cnt = 0, p = commit->parents; p; p = p->next) { + pst = locate_simplify_state(revs, p->item); + if (!pst->simplified) { + tail = &commit_list_insert(p->item, tail)->next; + cnt++; + } + } + if (cnt) { + tail = &commit_list_insert(commit, tail)->next; + return tail; + } + + /* + * Rewrite our list of parents. + */ + for (p = commit->parents; p; p = p->next) { + pst = locate_simplify_state(revs, p->item); + p->item = pst->simplified; + } + cnt = remove_duplicate_parents(commit); + + /* + * It is possible that we are a merge and one side branch + * does not have any commit that touches the given paths; + * in such a case, the immediate parents will be rewritten + * to different commits. + * + * o----X X: the commit we are looking at; + * / / o: a commit that touches the paths; + * ---o----' + * + * Further reduce the parents by removing redundant parents. + */ + if (1 < cnt) { + struct commit_list *h = reduce_heads(commit->parents); + cnt = commit_list_count(h); + free_commit_list(commit->parents); + commit->parents = h; + } + + /* + * A commit simplifies to itself if it is a root, if it is + * UNINTERESTING, if it touches the given paths, or if it is a + * merge and its parents simplifies to more than one commits + * (the first two cases are already handled at the beginning of + * this function). + * + * Otherwise, it simplifies to what its sole parent simplifies to. + */ + if (!cnt || + (commit->object.flags & UNINTERESTING) || + !(commit->object.flags & TREESAME) || + (1 < cnt)) + st->simplified = commit; + else { + pst = locate_simplify_state(revs, commit->parents->item); + st->simplified = pst->simplified; + } + return tail; +} + +static void simplify_merges(struct rev_info *revs) +{ + struct commit_list *list; + struct commit_list *yet_to_do, **tail; + + if (!revs->topo_order) + sort_in_topological_order(&revs->commits, revs->lifo); + if (!revs->prune) + return; + + /* feed the list reversed */ + yet_to_do = NULL; + for (list = revs->commits; list; list = list->next) + commit_list_insert(list->item, &yet_to_do); + while (yet_to_do) { + list = yet_to_do; + yet_to_do = NULL; + tail = &yet_to_do; + while (list) { + struct commit *commit = list->item; + struct commit_list *next = list->next; + free(list); + list = next; + tail = simplify_one(revs, commit, tail); + } + } + + /* clean up the result, removing the simplified ones */ + list = revs->commits; + revs->commits = NULL; + tail = &revs->commits; + while (list) { + struct commit *commit = list->item; + struct commit_list *next = list->next; + struct merge_simplify_state *st; + free(list); + list = next; + st = locate_simplify_state(revs, commit); + if (st->simplified == commit) + tail = &commit_list_insert(commit, tail)->next; + } +} + +static void set_children(struct rev_info *revs) +{ + struct commit_list *l; + for (l = revs->commits; l; l = l->next) { + struct commit *commit = l->item; + struct commit_list *p; + + for (p = commit->parents; p; p = p->next) + add_child(revs, p->item, commit); + } +} + +int prepare_revision_walk(struct rev_info *revs) +{ + int nr = revs->pending.nr; + struct object_array_entry *e, *list; + + e = list = revs->pending.objects; + revs->pending.nr = 0; + revs->pending.alloc = 0; + revs->pending.objects = NULL; + while (--nr >= 0) { + struct commit *commit = handle_commit(revs, e->item, e->name); + if (commit) { + if (!(commit->object.flags & SEEN)) { + commit->object.flags |= SEEN; + insert_by_date(commit, &revs->commits); + } + } + e++; + } + free(list); + + if (revs->no_walk) + return 0; + if (revs->limited) + if (limit_list(revs) < 0) + return -1; + if (revs->topo_order) + sort_in_topological_order(&revs->commits, revs->lifo); + if (revs->simplify_merges) + simplify_merges(revs); + if (revs->children.name) + set_children(revs); + return 0; +} + +enum rewrite_result { + rewrite_one_ok, + rewrite_one_noparents, + rewrite_one_error, +}; + +static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp) +{ + struct commit_list *cache = NULL; + + for (;;) { + struct commit *p = *pp; + if (!revs->limited) + if (add_parents_to_list(revs, p, &revs->commits, &cache) < 0) + return rewrite_one_error; + if (p->parents && p->parents->next) + return rewrite_one_ok; + if (p->object.flags & UNINTERESTING) + return rewrite_one_ok; + if (!(p->object.flags & TREESAME)) + return rewrite_one_ok; + if (!p->parents) + return rewrite_one_noparents; + *pp = p->parents->item; + } +} + +static int rewrite_parents(struct rev_info *revs, struct commit *commit) +{ + struct commit_list **pp = &commit->parents; + while (*pp) { + struct commit_list *parent = *pp; + switch (rewrite_one(revs, &parent->item)) { + case rewrite_one_ok: + break; + case rewrite_one_noparents: + *pp = parent->next; + continue; + case rewrite_one_error: + return -1; + } + pp = &parent->next; + } + remove_duplicate_parents(commit); + return 0; +} + +static int commit_match(struct commit *commit, struct rev_info *opt) +{ + if (!opt->grep_filter.pattern_list) + return 1; + return grep_buffer(&opt->grep_filter, + NULL, /* we say nothing, not even filename */ + commit->buffer, strlen(commit->buffer)); +} + +static inline int want_ancestry(struct rev_info *revs) +{ + return (revs->rewrite_parents || revs->children.name); +} + +enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit) +{ + if (commit->object.flags & SHOWN) + return commit_ignore; + if (revs->unpacked && has_sha1_pack(commit->object.sha1)) + return commit_ignore; + if (revs->show_all) + return commit_show; + if (commit->object.flags & UNINTERESTING) + return commit_ignore; + if (revs->min_age != -1 && (commit->date > revs->min_age)) + return commit_ignore; + if (revs->no_merges && commit->parents && commit->parents->next) + return commit_ignore; + if (revs->merges_only && !(commit->parents && commit->parents->next)) + return commit_ignore; + if (!commit_match(commit, revs)) + return commit_ignore; + if (revs->prune && revs->dense) { + /* Commit without changes? */ + if (commit->object.flags & TREESAME) { + /* drop merges unless we want parenthood */ + if (!want_ancestry(revs)) + return commit_ignore; + /* non-merge - always ignore it */ + if (!commit->parents || !commit->parents->next) + return commit_ignore; + } + } + return commit_show; +} + +enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit) +{ + enum commit_action action = get_commit_action(revs, commit); + + if (action == commit_show && + !revs->show_all && + revs->prune && revs->dense && want_ancestry(revs)) { + if (rewrite_parents(revs, commit) < 0) + return commit_error; + } + return action; +} + +static struct commit *get_revision_1(struct rev_info *revs) +{ + if (!revs->commits) + return NULL; + + do { + struct commit_list *entry = revs->commits; + struct commit *commit = entry->item; + + revs->commits = entry->next; + free(entry); + + if (revs->reflog_info) + fake_reflog_parent(revs->reflog_info, commit); + + /* + * If we haven't done the list limiting, we need to look at + * the parents here. We also need to do the date-based limiting + * that we'd otherwise have done in limit_list(). + */ + if (!revs->limited) { + if (revs->max_age != -1 && + (commit->date < revs->max_age)) + continue; + if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0) + die("Failed to traverse parents of commit %s", + sha1_to_hex(commit->object.sha1)); + } + + switch (simplify_commit(revs, commit)) { + case commit_ignore: + continue; + case commit_error: + die("Failed to simplify parents of commit %s", + sha1_to_hex(commit->object.sha1)); + default: + return commit; + } + } while (revs->commits); + return NULL; +} + +static void gc_boundary(struct object_array *array) +{ + unsigned nr = array->nr; + unsigned alloc = array->alloc; + struct object_array_entry *objects = array->objects; + + if (alloc <= nr) { + unsigned i, j; + for (i = j = 0; i < nr; i++) { + if (objects[i].item->flags & SHOWN) + continue; + if (i != j) + objects[j] = objects[i]; + j++; + } + for (i = j; i < nr; i++) + objects[i].item = NULL; + array->nr = j; + } +} + +static void create_boundary_commit_list(struct rev_info *revs) +{ + unsigned i; + struct commit *c; + struct object_array *array = &revs->boundary_commits; + struct object_array_entry *objects = array->objects; + + /* + * If revs->commits is non-NULL at this point, an error occurred in + * get_revision_1(). Ignore the error and continue printing the + * boundary commits anyway. (This is what the code has always + * done.) + */ + if (revs->commits) { + free_commit_list(revs->commits); + revs->commits = NULL; + } + + /* + * Put all of the actual boundary commits from revs->boundary_commits + * into revs->commits + */ + for (i = 0; i < array->nr; i++) { + c = (struct commit *)(objects[i].item); + if (!c) + continue; + if (!(c->object.flags & CHILD_SHOWN)) + continue; + if (c->object.flags & (SHOWN | BOUNDARY)) + continue; + c->object.flags |= BOUNDARY; + commit_list_insert(c, &revs->commits); + } + + /* + * If revs->topo_order is set, sort the boundary commits + * in topological order + */ + sort_in_topological_order(&revs->commits, revs->lifo); +} + +static struct commit *get_revision_internal(struct rev_info *revs) +{ + struct commit *c = NULL; + struct commit_list *l; + + if (revs->boundary == 2) { + /* + * All of the normal commits have already been returned, + * and we are now returning boundary commits. + * create_boundary_commit_list() has populated + * revs->commits with the remaining commits to return. + */ + c = pop_commit(&revs->commits); + if (c) + c->object.flags |= SHOWN; + return c; + } + + /* + * Now pick up what they want to give us + */ + c = get_revision_1(revs); + if (c) { + while (0 < revs->skip_count) { + revs->skip_count--; + c = get_revision_1(revs); + if (!c) + break; + } + } + + /* + * Check the max_count. + */ + switch (revs->max_count) { + case -1: + break; + case 0: + c = NULL; + break; + default: + revs->max_count--; + } + + if (c) + c->object.flags |= SHOWN; + + if (!revs->boundary) { + return c; + } + + if (!c) { + /* + * get_revision_1() runs out the commits, and + * we are done computing the boundaries. + * switch to boundary commits output mode. + */ + revs->boundary = 2; + + /* + * Update revs->commits to contain the list of + * boundary commits. + */ + create_boundary_commit_list(revs); + + return get_revision_internal(revs); + } + + /* + * boundary commits are the commits that are parents of the + * ones we got from get_revision_1() but they themselves are + * not returned from get_revision_1(). Before returning + * 'c', we need to mark its parents that they could be boundaries. + */ + + for (l = c->parents; l; l = l->next) { + struct object *p; + p = &(l->item->object); + if (p->flags & (CHILD_SHOWN | SHOWN)) + continue; + p->flags |= CHILD_SHOWN; + gc_boundary(&revs->boundary_commits); + add_object_array(p, NULL, &revs->boundary_commits); + } + + return c; +} + +struct commit *get_revision(struct rev_info *revs) +{ + struct commit *c; + struct commit_list *reversed; + + if (revs->reverse) { + reversed = NULL; + while ((c = get_revision_internal(revs))) { + commit_list_insert(c, &reversed); + } + revs->commits = reversed; + revs->reverse = 0; + revs->reverse_output_stage = 1; + } + + if (revs->reverse_output_stage) + return pop_commit(&revs->commits); + + c = get_revision_internal(revs); + if (c && revs->graph) + graph_update(revs->graph, c); + return c; +} From 2f5b712ee912803b6238dbce089f57f9162612d6 Mon Sep 17 00:00:00 2001 From: jiongle1 Date: Thu, 28 Mar 2024 17:05:12 +0800 Subject: [PATCH 3/3] commit patch 14888261 --- t/t9850-shell.sh | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100755 t/t9850-shell.sh diff --git a/t/t9850-shell.sh b/t/t9850-shell.sh new file mode 100755 index 00000000000000..2af476c3afcf0a --- /dev/null +++ b/t/t9850-shell.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +test_description='git shell tests' +. ./test-lib.sh + +test_expect_success 'shell allows upload-pack' ' + printf 0000 >input && + git upload-pack . expect && + git shell -c "git-upload-pack $SQ.$SQ" actual && + test_cmp expect actual +' + +test_expect_success 'shell forbids other commands' ' + test_must_fail git shell -c "git config foo.bar baz" +' + +test_expect_success 'shell forbids interactive use by default' ' + test_must_fail git shell +' + +test_expect_success 'shell allows interactive command' ' + mkdir git-shell-commands && + write_script git-shell-commands/ping <<-\EOF && + echo pong + EOF + echo pong >expect && + echo ping | git shell >actual && + test_cmp expect actual +' + +test_done