This script needs a bit more analysis to make sure all pathways are covered, but this CL fixes an obvious path. Change-Id: I2a8404b7a6eecd5cedba3daa20f63917a8d18482
		
			
				
	
	
		
			567 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			567 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
#!/bin/bash
 | 
						|
# Copyright (c) 2012, Code Aurora Forum. All rights reserved.
 | 
						|
#
 | 
						|
# Redistribution and use in source and binary forms, with or without
 | 
						|
# modification, are permitted provided that the following conditions are
 | 
						|
# met:
 | 
						|
#    # Redistributions of source code must retain the above copyright
 | 
						|
#       notice, this list of conditions and the following disclaimer.
 | 
						|
#    # Redistributions in binary form must reproduce the above
 | 
						|
#       copyright notice, this list of conditions and the following
 | 
						|
#       disclaimer in the documentation and/or other materials provided
 | 
						|
#       with the distribution.
 | 
						|
#    # Neither the name of Code Aurora Forum, Inc. nor the names of its
 | 
						|
#       contributors may be used to endorse or promote products derived
 | 
						|
#       from this software without specific prior written permission.
 | 
						|
#
 | 
						|
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
 | 
						|
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | 
						|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
 | 
						|
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 | 
						|
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 | 
						|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 | 
						|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 | 
						|
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 | 
						|
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 | 
						|
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 | 
						|
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
						|
 | 
						|
usage() { # error_message
 | 
						|
 | 
						|
    cat <<-EOF
 | 
						|
		usage: $(basename $0) [-unvt] [--noref] [--nolosse] [-r|--ratio number]
 | 
						|
		                      [git gc option...] git.repo
 | 
						|
 | 
						|
		-u|-h                usage/help
 | 
						|
		-v verbose
 | 
						|
		-n dry-run           don't actually repack anything
 | 
						|
		-t touch             treat repo as if it had been touched
 | 
						|
		--noref              avoid extra ref packing timestamp checking
 | 
						|
		--noloose            do not run just because there are loose object dirs
 | 
						|
		                     (repacking may still run if they are referenced)
 | 
						|
		-r ratio <number>    packfile ratio to aim for (default 10)
 | 
						|
 | 
						|
		git gc option        will be passed as args to git gc
 | 
						|
 | 
						|
		git.repo             to run gc against
 | 
						|
 | 
						|
		Garbage collect using a pseudo logarithmic packfile maintenance
 | 
						|
		approach.  This approach attempts to minimize packfile churn
 | 
						|
		by keeping several generations of varying sized packfiles around
 | 
						|
		and only consolidating packfiles (or loose objects) which are
 | 
						|
		either new packfiles, or packfiles close to the same size as
 | 
						|
		another packfile.
 | 
						|
 | 
						|
		An estimate is used to predict when rollups (one consolidation
 | 
						|
		would cause another consolidation) would occur so that this
 | 
						|
		rollup can be done all at once via a single repack.  This reduces
 | 
						|
		both the runtime and the pack file churn in rollup cases.
 | 
						|
 | 
						|
		Approach: plan each consolidation by creating a table like this:
 | 
						|
 | 
						|
		Id Keep Size           Sha1(or consolidation list)      Actions(repack down up note)
 | 
						|
		1     - 11356          9052edfb7392646cd4e5f362b953675985f01f96 y - - New
 | 
						|
		2     - 429088         010904d5c11cd26a79fda91b01ab454d1001b402 y - - New
 | 
						|
		c1    - 440444         [1,2]                                    - - -
 | 
						|
 | 
						|
		Id:    numbers preceded by a c are estimated "c pack" files
 | 
						|
		Keep:  - none, k private keep, o our keep
 | 
						|
		Size:  in disk blocks (default du output)
 | 
						|
		Sha1:  of packfile, or consolidation list of packfile ids
 | 
						|
		Actions
 | 
						|
		repack: - n no, y yes
 | 
						|
		down:   - noop, ^ consolidate with a file above
 | 
						|
		up:     - noop, v consolidate with a file below
 | 
						|
		note:   Human description of script decisions:
 | 
						|
		         New (file is a new packfile)
 | 
						|
		         Consolidate with:<list of packfile ids>
 | 
						|
		         (too far from:<list of packfile ids>)
 | 
						|
 | 
						|
		On the first pass, always consolidate any new packfiles along
 | 
						|
		with loose objects and along with any packfiles which are within
 | 
						|
		the ratio size of their predecessors (note, the list is ordered
 | 
						|
		by increasing size).  After each consolidation, insert a fake
 | 
						|
		consolidation, or "c pack", to naively represent the size and
 | 
						|
		ordered positioning of the anticipated new consolidated pack.
 | 
						|
		Every time a new pack is planned, rescan the list in case the
 | 
						|
		new "c pack" would cause more consolidation...
 | 
						|
 | 
						|
		Once the packfiles which need consolidation are determined, the
 | 
						|
		packfiles which will not be consolidated are marked with a .keep
 | 
						|
		file, and those which will be consolidated will have their .keep
 | 
						|
		removed if they have one.  Thus, the packfiles with a .keep will
 | 
						|
		not get repacked.
 | 
						|
 | 
						|
		Packfile consolidation is determined by the --ratio parameter
 | 
						|
		(default is 10).  This ratio is somewhat of a tradeoff.  The
 | 
						|
		smaller the number, the more packfiles will be kept on average;
 | 
						|
		this increases disk utilization somewhat.  However, a larger
 | 
						|
		ratio causes greater churn and may increase disk utilization due
 | 
						|
		to deleted packfiles not being reclaimed since they may still be
 | 
						|
		kept open by long running applications such as Gerrit.  Sane
 | 
						|
		ratio values are probably between 2 and 10.  Since most
 | 
						|
		consolidations actually end up smaller than the estimated
 | 
						|
		consolidated packfile size (due to compression), the true ratio
 | 
						|
		achieved will likely be 1 to 2 greater than the target ratio.
 | 
						|
		The smaller the target ratio, the greater this discrepancy.
 | 
						|
 | 
						|
		Finally, attempt to skip garbage collection entirely on untouched
 | 
						|
		repos.  In order to determine if a repo has been touched, use the
 | 
						|
		timestamp on the script's keep files, if any relevant file/dir
 | 
						|
		is newer than a keep marker file, assume that the repo has been
 | 
						|
		touched and gc needs to run.  Also assume gc needs to run whenever
 | 
						|
		there are loose object dirs since they may contain untouched
 | 
						|
		unreferenced loose objects which need to be pruned (once they
 | 
						|
		expire).
 | 
						|
 | 
						|
		In order to allow the keep files to be an effective timestamp
 | 
						|
		marker to detect relevant changes in a repo since the last run,
 | 
						|
		all relevant files and directories which may be modified during a
 | 
						|
		gc run (even during a noop gc run), must have their timestamps
 | 
						|
		reset to the same time as the keep files or gc will always run
 | 
						|
		even on untouched repos.  The relevant files/dirs are all those
 | 
						|
		files and directories which garbage collection, object packing,
 | 
						|
		ref packing and pruning might change during noop actions.
 | 
						|
EOF
 | 
						|
 | 
						|
    [ -n "$1" ] && info "ERROR $1"
 | 
						|
 | 
						|
    exit 128
 | 
						|
}
 | 
						|
 | 
						|
debug() { [ -n "$SW_V" ] && info "$1" ; }
 | 
						|
info() { echo "$1" >&2 ; }
 | 
						|
 | 
						|
array_copy() { #v2 # array_src array_dst
 | 
						|
    local src=$1 dst=$2
 | 
						|
    local s i=0
 | 
						|
    eval s=\${#$src[@]}
 | 
						|
    while [ $i -lt $s ] ; do
 | 
						|
        eval $dst[$i]=\"\${$src[$i]}\"
 | 
						|
        i=$(($i + 1))
 | 
						|
    done
 | 
						|
}
 | 
						|
 | 
						|
array_equals() { #v2 # array_name [vals...]
 | 
						|
    local a=$1 ; shift
 | 
						|
    local s=0 t=() val
 | 
						|
    array_copy "$a" t
 | 
						|
    for s in "${!t[@]}" ; do s=$((s+1)) ; done
 | 
						|
    [ "$s" -ne "$#" ] && return 1
 | 
						|
    for val in "${t[@]}" ; do
 | 
						|
        [ "$val" = "$1" ] || return 2
 | 
						|
        shift
 | 
						|
    done
 | 
						|
    return 0
 | 
						|
}
 | 
						|
 | 
						|
packs_sizes() { # git.repo > "size pack"...
 | 
						|
    du -s "$1"/objects/pack/pack-$SHA1.pack | sort -n 2> /dev/null
 | 
						|
}
 | 
						|
 | 
						|
is_ourkeep() { grep -q "$KEEP" "$1" 2> /dev/null ; } # keep
 | 
						|
has_ourkeep() { is_ourkeep "$(keep_for "$1")" ; } # pack
 | 
						|
has_keep() { [ -f "$(keep_for "$1")" ] ; } # pack
 | 
						|
is_repo() { [ -d "$1/objects" ] && [ -d "$1/refs/heads" ] ; } # git.repo
 | 
						|
 | 
						|
keep() { # pack   # returns true if we added our keep
 | 
						|
    keep=$(keep_for "$1")
 | 
						|
    [ -f "$keep" ] && return 1
 | 
						|
    echo "$KEEP" > "$keep"
 | 
						|
    return 0
 | 
						|
}
 | 
						|
 | 
						|
keep_for() { # packfile > keepfile
 | 
						|
    local keep=$(echo "$1" | sed -es'/\.pack$/.keep/')
 | 
						|
    [ "${keep/.keep}" = "$keep" ] && return 1
 | 
						|
    echo "$keep"
 | 
						|
}
 | 
						|
 | 
						|
idx_for() { # packfile > idxfile
 | 
						|
    local idx=$(echo "$1" | sed -es'/\.pack$/.idx/')
 | 
						|
    [ "${idx/.idx}" = "$idx" ] && return 1
 | 
						|
    echo "$idx"
 | 
						|
}
 | 
						|
 | 
						|
# pack_or_keep_file > sha
 | 
						|
sha_for() { echo "$1" | sed -es'|\(.*/\)*pack-\([^.]*\)\..*$|\2|' ; }
 | 
						|
 | 
						|
private_keeps() { # git.repo -> sets pkeeps
 | 
						|
    local repo=$1 ary=$2
 | 
						|
    local keep keeps=("$repo"/objects/pack/pack-$SHA1.keep)
 | 
						|
    pkeeps=()
 | 
						|
    for keep in "${keeps[@]}" ; do
 | 
						|
        is_ourkeep "$keep" || pkeeps=("${pkeeps[@]}" "$keep")
 | 
						|
    done
 | 
						|
}
 | 
						|
 | 
						|
is_tooclose() { [ "$(($1 * $RATIO))" -gt "$2" ] ; } # smaller larger
 | 
						|
 | 
						|
unique() { # [args...] > unique_words
 | 
						|
    local lines=$(while [ $# -gt 0 ] ; do echo "$1" ; shift ; done)
 | 
						|
    lines=$(echo "$lines" | sort -u)
 | 
						|
    echo $lines  # as words
 | 
						|
}
 | 
						|
 | 
						|
outfs() { # fs [args...] > argfs...
 | 
						|
    local fs=$1 ; shift
 | 
						|
    [ $# -gt 0 ] && echo -n "$1" ; shift
 | 
						|
    while [ $# -gt 0 ] ; do echo -n "$fs$1" ; shift ; done
 | 
						|
}
 | 
						|
 | 
						|
sort_list() { # < list > formatted_list
 | 
						|
    # n has_keep size sha repack down up note
 | 
						|
    awk '{ note=$8; for(i=8;i<NF;i++) note=note " "$(i+1)
 | 
						|
           printf("%-5s %s %-14s %-40s %s %s %s %s\n", \
 | 
						|
                     $1,$2,   $3,  $4, $5,$6,$7,note)}' |\
 | 
						|
        sort -k 3,3n -k 1,1n
 | 
						|
}
 | 
						|
 | 
						|
is_touched() { # git.repo
 | 
						|
    local repo=$1
 | 
						|
    local loose keep ours newer
 | 
						|
    [ -n "$SW_T" ] && { debug "$SW_T -> treat as touched" ; return 0 ; }
 | 
						|
 | 
						|
    if [ -z "$SW_LOOSE" ] ; then
 | 
						|
        # If there are loose objects, they may need to be pruned,
 | 
						|
        # run even if nothing has really been touched.
 | 
						|
        loose=$(find "$repo/objects" -type d \
 | 
						|
                      -wholename "$repo/objects/[0-9][0-9]"
 | 
						|
                      -print -quit 2>/dev/null)
 | 
						|
        [ -n "$loose" ] && { info "There are loose object directories" ; return 0 ; }
 | 
						|
    fi
 | 
						|
 | 
						|
    # If we don't have a keep, the current packfiles may not have been
 | 
						|
    # compressed with the current gc policy (gc may never have been run),
 | 
						|
    # so run at least once to repack everything.  Also, we need a marker
 | 
						|
    # file for timestamp tracking (a dir needs to detect changes within
 | 
						|
    # it, so it cannot be a marker) and our keeps are something we control,
 | 
						|
    # use them.
 | 
						|
    for keep in "$repo"/objects/pack/pack-$SHA1.keep ; do
 | 
						|
        is_ourkeep "$keep" && { ours=$keep ; break ; }
 | 
						|
    done
 | 
						|
    [ -z "$ours" ] && { info 'We have no keep (we have never run?): run' ; return 0 ; }
 | 
						|
 | 
						|
    debug "Our timestamp keep: $ours"
 | 
						|
    # The wholename stuff seems to get touched by a noop git gc
 | 
						|
    newer=$(find "$repo/objects" "$repo/refs" "$repo/packed-refs" \
 | 
						|
                  '!' -wholename "$repo/objects/info" \
 | 
						|
                  '!' -wholename "$repo/objects/info/*" \
 | 
						|
                  -newer "$ours" \
 | 
						|
                  -print -quit 2>/dev/null)
 | 
						|
    [ -z "$newer" ] && return 1
 | 
						|
 | 
						|
    info "Touched since last run: $newer"
 | 
						|
    return 0
 | 
						|
}
 | 
						|
 | 
						|
touch_refs() { # git.repo start_date refs
 | 
						|
    local repo=$1 start_date=$2 refs=$3
 | 
						|
    (
 | 
						|
        debug "Setting start date($start_date) on unpacked refs:"
 | 
						|
        debug "$refs"
 | 
						|
        cd "$repo/refs" || return
 | 
						|
        # safe to assume no newlines in a ref name
 | 
						|
        echo "$refs" | xargs -d '\n' -n 1 touch -c -d "$start_date"
 | 
						|
    )
 | 
						|
}
 | 
						|
 | 
						|
set_start_date() { # git.repo start_date refs refdirs packedrefs [packs]
 | 
						|
    local repo=$1 start_date=$2 refs=$3 refdirs=$4 packedrefs=$5 ; shift 5
 | 
						|
    local pack keep idx repacked
 | 
						|
 | 
						|
    # This stuff is touched during object packs
 | 
						|
    while [ $# -gt 0 ] ; do
 | 
						|
        pack=$1 ; shift
 | 
						|
        keep="$(keep_for "$pack")"
 | 
						|
        idx="$(idx_for "$pack")"
 | 
						|
        touch -c -d "$start_date" "$pack" "$keep" "$idx"
 | 
						|
        debug "Setting start date on: $pack $keep $idx"
 | 
						|
    done
 | 
						|
    # This will prevent us from detecting any deletes in the pack dir
 | 
						|
    # since gc ran, except for private keeps which we are checking
 | 
						|
    # manually.  But there really shouldn't be any other relevant deletes
 | 
						|
    # in this dir which should cause us to rerun next time, deleting a
 | 
						|
    # pack or index file by anything but gc would be bad!
 | 
						|
    debug "Setting start date on pack dir: $start_date"
 | 
						|
    touch -c -d "$start_date" "$repo/objects/pack"
 | 
						|
 | 
						|
 | 
						|
    if [ -z "$SW_REFS" ] ; then
 | 
						|
        repacked=$(find "$repo/packed-refs" -newer "$repo/objects/pack"
 | 
						|
                      -print -quit 2>/dev/null)
 | 
						|
        if [ -n "$repacked" ] ; then
 | 
						|
            # The ref dirs and packed-ref files seem to get touched even on
 | 
						|
            # a noop refpacking
 | 
						|
            debug "Setting start date on packed-refs"
 | 
						|
            touch -c -d "$start_date" "$repo/packed-refs"
 | 
						|
            touch_refs "$repo" "$start_date" "$refdirs"
 | 
						|
 | 
						|
            # A ref repack does not imply a ref change, but since it is
 | 
						|
            # hard to tell, simply assume so
 | 
						|
            if [ "$refs" != "$(cd "$repo/refs" ; find -depth)" ] || \
 | 
						|
               [ "$packedrefs" != "$(<"$repo/packed-refs")" ] ; then
 | 
						|
                # We retouch if needed (instead of simply checking then
 | 
						|
                # touching) to avoid a race between the check and the set.
 | 
						|
                debug "  but refs actually got packed, so retouch packed-refs"
 | 
						|
                touch -c "$repo/packed-refs"
 | 
						|
            fi
 | 
						|
        fi
 | 
						|
    fi
 | 
						|
}
 | 
						|
 | 
						|
note_consolidate() { # note entry > note (no duplicated consolidated entries)
 | 
						|
    local note=$1 entry=$2
 | 
						|
    local entries=() ifs=$IFS
 | 
						|
    if  echo "$note" | grep -q 'Consolidate with:[0-9,c]' ; then
 | 
						|
        IFS=,
 | 
						|
        entries=( $(echo "$note" | sed -es'/^.*Consolidate with:\([0-9,c]*\).*$/\1/') )
 | 
						|
        note=( $(echo "$note" | sed -es'/Consolidate with:[0-9,c]*//') )
 | 
						|
        IFS=$ifs
 | 
						|
    fi
 | 
						|
    entries=( $(unique "${entries[@]}" "$entry") )
 | 
						|
    echo "$note Consolidate with:$(outfs , "${entries[@]}")"
 | 
						|
}
 | 
						|
 | 
						|
note_toofar() { # note entry > note (no duplicated "too far" entries)
 | 
						|
    local note=$1 entry=$2
 | 
						|
    local entries=() ifs=$IFS
 | 
						|
    if  echo "$note" | grep -q '(too far from:[0-9,c]*)' ; then
 | 
						|
        IFS=,
 | 
						|
        entries=( $(echo "$note" | sed -es'/^.*(too far from:\([0-9,c]*\)).*$/\1/') )
 | 
						|
        note=( $(echo "$note" | sed -es'/(too far from:[0-9,c]*)//') )
 | 
						|
        IFS=$ifs
 | 
						|
    fi
 | 
						|
    entries=( $(unique "${entries[@]}" "$entry") )
 | 
						|
    echo "$note (too far from:$(outfs , "${entries[@]}"))"
 | 
						|
}
 | 
						|
 | 
						|
last_entry() { # isRepack pline repackline > last_rows_entry
 | 
						|
    local size_hit=$1 pline=$2 repackline=$3
 | 
						|
    if [ -n "$pline" ] ; then
 | 
						|
        if [ -n "$size_hit" ] ; then
 | 
						|
            echo "$repack_line"
 | 
						|
        else
 | 
						|
            echo "$pline"
 | 
						|
        fi
 | 
						|
    fi
 | 
						|
}
 | 
						|
 | 
						|
init_list() { # git.repo > shortlist
 | 
						|
    local repo=$1
 | 
						|
    local file
 | 
						|
    local n has_keep size sha repack
 | 
						|
 | 
						|
    packs_sizes "$1" | {
 | 
						|
        while read size file ; do
 | 
						|
            n=$((n+1))
 | 
						|
            repack=n
 | 
						|
            has_keep=-
 | 
						|
            if has_keep "$file" ; then
 | 
						|
                has_keep=k
 | 
						|
                has_ourkeep "$file" && has_keep=o
 | 
						|
            fi
 | 
						|
            sha=$(sha_for "$file")
 | 
						|
            echo "$n $has_keep $size $sha $repack"
 | 
						|
        done
 | 
						|
    } | sort_list
 | 
						|
}
 | 
						|
 | 
						|
consolidate_list() { # run < list > list
 | 
						|
    local run=$1
 | 
						|
    local sum=0 psize=0 sum_size=0 size_hit pn clist pline repackline
 | 
						|
    local n has_keep size sha repack down up note
 | 
						|
 | 
						|
    {
 | 
						|
        while read n has_keep size sha repack down up note; do
 | 
						|
            [ -z "$up" ] && up='-'
 | 
						|
            [ -z "$down" ] && down="-"
 | 
						|
 | 
						|
            if [ "$has_keep" = "k" ] ; then
 | 
						|
                echo "$n $has_keep $size $sha $repack - - Private"
 | 
						|
                continue
 | 
						|
            fi
 | 
						|
 | 
						|
            if [ "$repack" = "n" ] ; then
 | 
						|
                if is_tooclose $psize $size ; then
 | 
						|
                    size_hit=y
 | 
						|
                    repack=y
 | 
						|
                    sum=$(($sum + $sum_size + $size))
 | 
						|
                    sum_size=0 # Prevents double summing this entry
 | 
						|
                    clist=($(unique "${clist[@]}" $pn $n))
 | 
						|
                    down="^"
 | 
						|
                    [ "$has_keep" = "-" ] && note="$note New +"
 | 
						|
                    note=$(note_consolidate "$note" "$pn")
 | 
						|
                elif [ "$has_keep" = "-" ] ; then
 | 
						|
                    repack=y
 | 
						|
                    sum=$(($sum + $size))
 | 
						|
                    sum_size=0 # Prevents double summing this entry
 | 
						|
                    clist=($(unique "${clist[@]}" $n))
 | 
						|
                    note="$note New"
 | 
						|
                elif [ $psize -ne 0 ] ; then
 | 
						|
                    sum_size=$size
 | 
						|
                    down="!"
 | 
						|
                    note=$(note_toofar "$note" "$pn")
 | 
						|
                else
 | 
						|
                    sum_size=$size
 | 
						|
                fi
 | 
						|
            else
 | 
						|
                sum_size=$size
 | 
						|
            fi
 | 
						|
 | 
						|
            # By preventing "c files" (consolidated) from being marked
 | 
						|
            # "repack" they won't get keeps
 | 
						|
            repack2=y
 | 
						|
            [ "${n/c}" != "$n" ] && { repack=- ; repack2=- ; }
 | 
						|
 | 
						|
            last_entry "$size_hit" "$pline" "$repack_line"
 | 
						|
            # Delay the printout until we know whether we are
 | 
						|
            # being consolidated with the entry following us
 | 
						|
            # (we won't know until the next iteration).
 | 
						|
            # size_hit is used to determine which of the lines
 | 
						|
            # below will actually get printed above on the next
 | 
						|
            # iteration.
 | 
						|
            pline="$n $has_keep $size $sha $repack $down $up $note"
 | 
						|
            repack_line="$n $has_keep $size $sha $repack2 $down v $note"
 | 
						|
 | 
						|
            pn=$n ; psize=$size # previous entry data
 | 
						|
            size_hit='' # will not be consolidated up
 | 
						|
 | 
						|
        done
 | 
						|
        last_entry "$size_hit" "$pline" "$repack_line"
 | 
						|
 | 
						|
        [ $sum -gt 0 ] && echo "c$run - $sum [$(outfs , "${clist[@]}")] - - -"
 | 
						|
 | 
						|
    } | sort_list
 | 
						|
}
 | 
						|
 | 
						|
process_list() { # git.repo > list
 | 
						|
    local list=$(init_list "$1")  plist run=0
 | 
						|
 | 
						|
    while true ; do
 | 
						|
        plist=$list
 | 
						|
        run=$((run +1))
 | 
						|
        list=$(echo "$list" | consolidate_list "$run")
 | 
						|
        if [ "$plist" != "$list" ] ; then
 | 
						|
            debug "------------------------------------------------------------------------------------"
 | 
						|
            debug "$HEADER"
 | 
						|
            debug "$list"
 | 
						|
        else
 | 
						|
            break
 | 
						|
        fi
 | 
						|
    done
 | 
						|
    debug "------------------------------------------------------------------------------------"
 | 
						|
    echo "$list"
 | 
						|
}
 | 
						|
 | 
						|
repack_list() { # git.repo < list
 | 
						|
    local repo=$1
 | 
						|
    local start_date newpacks=0 pkeeps keeps=1 refs refdirs rtn
 | 
						|
    local packedrefs=$(<"$repo/packed-refs")
 | 
						|
 | 
						|
    # so they don't appear touched after a noop refpacking
 | 
						|
    if [ -z "$SW_REFS" ] ; then
 | 
						|
        refs=$(cd "$repo/refs" ; find -depth)
 | 
						|
        refdirs=$(cd "$repo/refs" ; find -type d -depth)
 | 
						|
        debug "Before refs:"
 | 
						|
        debug "$refs"
 | 
						|
    fi
 | 
						|
 | 
						|
    # Find a private keep snapshot which has not changed from
 | 
						|
    # before our start_date so private keep deletions during gc
 | 
						|
    # can be detected
 | 
						|
    while ! array_equals pkeeps "${keeps[@]}" ; do
 | 
						|
       debug "Getting a private keep snapshot"
 | 
						|
       private_keeps "$repo"
 | 
						|
       keeps=("${pkeeps[@]}")
 | 
						|
       debug "before keeps: ${keeps[*]}"
 | 
						|
       start_date=$(date)
 | 
						|
       private_keeps "$repo"
 | 
						|
       debug "after keeps: ${pkeeps[*]}"
 | 
						|
    done
 | 
						|
 | 
						|
    while read n has_keep size sha repack down up note; do
 | 
						|
        if [ "$repack" = "y" ] ; then
 | 
						|
            keep="$repo/objects/pack/pack-$sha.keep"
 | 
						|
            info "Repacking $repo/objects/pack/pack-$sha.pack"
 | 
						|
            [ -f "$keep" ] && rm -f "$keep"
 | 
						|
        fi
 | 
						|
    done
 | 
						|
 | 
						|
    ( cd "$repo" && git gc "${GC_OPTS[@]}" ) ; rtn=$?
 | 
						|
 | 
						|
    # Mark any files withoug a .keep with our .keep
 | 
						|
    packs=("$repo"/objects/pack/pack-$SHA1.pack)
 | 
						|
    for pack in "${packs[@]}" ; do
 | 
						|
        if keep "$pack" ; then
 | 
						|
            info "New pack: $pack"
 | 
						|
            newpacks=$((newpacks+1))
 | 
						|
        fi
 | 
						|
    done
 | 
						|
 | 
						|
    # Record start_time.  If there is more than 1 new packfile, we
 | 
						|
    # don't want to risk touching it with an older date since that
 | 
						|
    # would prevent consolidation on the next run.  If the private
 | 
						|
    # keeps have changed, then we should run next time no matter what.
 | 
						|
    if [ $newpacks -le 1 ] || ! array_equals pkeeps "${keeps[@]}" ; then
 | 
						|
        set_start_date "$repo" "$start_date" "$refs" "$refdirs" "$packedrefs" "${packs[@]}"
 | 
						|
    fi
 | 
						|
 | 
						|
    return $rtn # we really only care about the gc error code
 | 
						|
}
 | 
						|
 | 
						|
git_gc() { # git.repo
 | 
						|
    local list=$(process_list "$1")
 | 
						|
    if [ -z "$SW_V" ] ; then
 | 
						|
        info "Running $PROG on $1.  git gc options: ${GC_OPTS[@]}"
 | 
						|
        echo "$HEADER" >&2
 | 
						|
        echo "$list" >&2 ;
 | 
						|
    fi
 | 
						|
    echo "$list" | repack_list "$1"
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PROG=$(basename "$0")
 | 
						|
HEADER="Id Keep Size           Sha1(or consolidation list)      Actions(repack down up note)"
 | 
						|
KEEP=git-exproll
 | 
						|
HEX='[0-9a-f]'
 | 
						|
HEX10=$HEX$HEX$HEX$HEX$HEX$HEX$HEX$HEX$HEX$HEX
 | 
						|
SHA1=$HEX10$HEX10$HEX10$HEX10
 | 
						|
 | 
						|
RATIO=10
 | 
						|
SW_N='' ; SW_V='' ; SW_T='' ; SW_REFS='' ; SW_LOOSE='' ; GC_OPTS=()
 | 
						|
while [ $# -gt 0 ] ; do
 | 
						|
    case "$1" in
 | 
						|
        -u|-h)  usage ;;
 | 
						|
        -n)  SW_N="$1" ;;
 | 
						|
        -v)  SW_V="$1" ;;
 | 
						|
 | 
						|
        -t)  SW_T="$1" ;;
 | 
						|
        --norefs)  SW_REFS="$1" ;;
 | 
						|
        --noloose) SW_LOOSE="$1" ;;
 | 
						|
 | 
						|
        -r|--ratio)  shift ; RATIO="$1" ;;
 | 
						|
 | 
						|
        *)  [ $# -le 1 ] && break
 | 
						|
            GC_OPTS=( "${GC_OPTS[@]}" "$1" )
 | 
						|
            ;;
 | 
						|
    esac
 | 
						|
    shift
 | 
						|
done
 | 
						|
 | 
						|
 | 
						|
REPO="$1"
 | 
						|
if ! is_repo "$REPO" ; then
 | 
						|
    REPO=$REPO/.git
 | 
						|
    is_repo "$REPO" || usage "($1) is not likely a git repo"
 | 
						|
fi
 | 
						|
 | 
						|
 | 
						|
if [ -z "$SW_N" ] ; then
 | 
						|
    is_touched "$REPO" || { info "Repo untouched since last run" ; exit ; }
 | 
						|
    git_gc "$REPO"
 | 
						|
else
 | 
						|
    is_touched "$REPO" || info "Repo untouched since last run, analyze anyway."
 | 
						|
    process_list "$REPO" >&2
 | 
						|
fi
 |