git-submodule.shon commit Add selftest for 'git submodule foreach' (d69ecf6)
   1#!/bin/sh
   2#
   3# git-submodules.sh: add, init, update or list git submodules
   4#
   5# Copyright (c) 2007 Lars Hjemli
   6
   7dashless=$(basename "$0" | sed -e 's/-/ /')
   8USAGE="[--quiet] add [-b branch] [--reference <repository>] [--] <repository> <path>
   9   or: $dashless [--quiet] status [--cached] [--] [<path>...]
  10   or: $dashless [--quiet] init [--] [<path>...]
  11   or: $dashless [--quiet] update [--init] [-N|--no-fetch] [--rebase] [--reference <repository>] [--merge] [--] [<path>...]
  12   or: $dashless [--quiet] summary [--cached] [--summary-limit <n>] [commit] [--] [<path>...]
  13   or: $dashless [--quiet] foreach <command>
  14   or: $dashless [--quiet] sync [--] [<path>...]"
  15OPTIONS_SPEC=
  16. git-sh-setup
  17. git-parse-remote
  18require_work_tree
  19
  20command=
  21branch=
  22reference=
  23cached=
  24nofetch=
  25update=
  26
  27# Resolve relative url by appending to parent's url
  28resolve_relative_url ()
  29{
  30        remote=$(get_default_remote)
  31        remoteurl=$(git config "remote.$remote.url") ||
  32                die "remote ($remote) does not have a url defined in .git/config"
  33        url="$1"
  34        remoteurl=${remoteurl%/}
  35        while test -n "$url"
  36        do
  37                case "$url" in
  38                ../*)
  39                        url="${url#../}"
  40                        remoteurl="${remoteurl%/*}"
  41                        ;;
  42                ./*)
  43                        url="${url#./}"
  44                        ;;
  45                *)
  46                        break;;
  47                esac
  48        done
  49        echo "$remoteurl/${url%/}"
  50}
  51
  52#
  53# Get submodule info for registered submodules
  54# $@ = path to limit submodule list
  55#
  56module_list()
  57{
  58        git ls-files --error-unmatch --stage -- "$@" | grep '^160000 '
  59}
  60
  61#
  62# Map submodule path to submodule name
  63#
  64# $1 = path
  65#
  66module_name()
  67{
  68        # Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
  69        re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
  70        name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
  71                sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
  72       test -z "$name" &&
  73       die "No submodule mapping found in .gitmodules for path '$path'"
  74       echo "$name"
  75}
  76
  77#
  78# Clone a submodule
  79#
  80# Prior to calling, cmd_update checks that a possibly existing
  81# path is not a git repository.
  82# Likewise, cmd_add checks that path does not exist at all,
  83# since it is the location of a new submodule.
  84#
  85module_clone()
  86{
  87        path=$1
  88        url=$2
  89        reference="$3"
  90
  91        # If there already is a directory at the submodule path,
  92        # expect it to be empty (since that is the default checkout
  93        # action) and try to remove it.
  94        # Note: if $path is a symlink to a directory the test will
  95        # succeed but the rmdir will fail. We might want to fix this.
  96        if test -d "$path"
  97        then
  98                rmdir "$path" 2>/dev/null ||
  99                die "Directory '$path' exist, but is neither empty nor a git repository"
 100        fi
 101
 102        test -e "$path" &&
 103        die "A file already exist at path '$path'"
 104
 105        if test -n "$reference"
 106        then
 107                git-clone "$reference" -n "$url" "$path"
 108        else
 109                git-clone -n "$url" "$path"
 110        fi ||
 111        die "Clone of '$url' into submodule path '$path' failed"
 112}
 113
 114#
 115# Add a new submodule to the working tree, .gitmodules and the index
 116#
 117# $@ = repo path
 118#
 119# optional branch is stored in global branch variable
 120#
 121cmd_add()
 122{
 123        # parse $args after "submodule ... add".
 124        while test $# -ne 0
 125        do
 126                case "$1" in
 127                -b | --branch)
 128                        case "$2" in '') usage ;; esac
 129                        branch=$2
 130                        shift
 131                        ;;
 132                -q|--quiet)
 133                        GIT_QUIET=1
 134                        ;;
 135                --reference)
 136                        case "$2" in '') usage ;; esac
 137                        reference="--reference=$2"
 138                        shift
 139                        ;;
 140                --reference=*)
 141                        reference="$1"
 142                        shift
 143                        ;;
 144                --)
 145                        shift
 146                        break
 147                        ;;
 148                -*)
 149                        usage
 150                        ;;
 151                *)
 152                        break
 153                        ;;
 154                esac
 155                shift
 156        done
 157
 158        repo=$1
 159        path=$2
 160
 161        if test -z "$repo" -o -z "$path"; then
 162                usage
 163        fi
 164
 165        # assure repo is absolute or relative to parent
 166        case "$repo" in
 167        ./*|../*)
 168                # dereference source url relative to parent's url
 169                realrepo=$(resolve_relative_url "$repo") || exit
 170                ;;
 171        *:*|/*)
 172                # absolute url
 173                realrepo=$repo
 174                ;;
 175        *)
 176                die "repo URL: '$repo' must be absolute or begin with ./|../"
 177        ;;
 178        esac
 179
 180        # normalize path:
 181        # multiple //; leading ./; /./; /../; trailing /
 182        path=$(printf '%s/\n' "$path" |
 183                sed -e '
 184                        s|//*|/|g
 185                        s|^\(\./\)*||
 186                        s|/\./|/|g
 187                        :start
 188                        s|\([^/]*\)/\.\./||
 189                        tstart
 190                        s|/*$||
 191                ')
 192        git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
 193        die "'$path' already exists in the index"
 194
 195        # perhaps the path exists and is already a git repo, else clone it
 196        if test -e "$path"
 197        then
 198                if test -d "$path"/.git -o -f "$path"/.git
 199                then
 200                        echo "Adding existing repo at '$path' to the index"
 201                else
 202                        die "'$path' already exists and is not a valid git repo"
 203                fi
 204
 205                case "$repo" in
 206                ./*|../*)
 207                        url=$(resolve_relative_url "$repo") || exit
 208                    ;;
 209                *)
 210                        url="$repo"
 211                        ;;
 212                esac
 213                git config submodule."$path".url "$url"
 214        else
 215
 216                module_clone "$path" "$realrepo" "$reference" || exit
 217                (
 218                        unset GIT_DIR
 219                        cd "$path" &&
 220                        # ash fails to wordsplit ${branch:+-b "$branch"...}
 221                        case "$branch" in
 222                        '') git checkout -f -q ;;
 223                        ?*) git checkout -f -q -b "$branch" "origin/$branch" ;;
 224                        esac
 225                ) || die "Unable to checkout submodule '$path'"
 226        fi
 227
 228        git add "$path" ||
 229        die "Failed to add submodule '$path'"
 230
 231        git config -f .gitmodules submodule."$path".path "$path" &&
 232        git config -f .gitmodules submodule."$path".url "$repo" &&
 233        git add .gitmodules ||
 234        die "Failed to register submodule '$path'"
 235}
 236
 237#
 238# Execute an arbitrary command sequence in each checked out
 239# submodule
 240#
 241# $@ = command to execute
 242#
 243cmd_foreach()
 244{
 245        # parse $args after "submodule ... foreach".
 246        while test $# -ne 0
 247        do
 248                case "$1" in
 249                -q|--quiet)
 250                        GIT_QUIET=1
 251                        ;;
 252                -*)
 253                        usage
 254                        ;;
 255                *)
 256                        break
 257                        ;;
 258                esac
 259                shift
 260        done
 261
 262        module_list |
 263        while read mode sha1 stage path
 264        do
 265                if test -e "$path"/.git
 266                then
 267                        say "Entering '$path'"
 268                        name=$(module_name "$path")
 269                        (cd "$path" && eval "$@") ||
 270                        die "Stopping at '$path'; script returned non-zero status."
 271                fi
 272        done
 273}
 274
 275#
 276# Register submodules in .git/config
 277#
 278# $@ = requested paths (default to all)
 279#
 280cmd_init()
 281{
 282        # parse $args after "submodule ... init".
 283        while test $# -ne 0
 284        do
 285                case "$1" in
 286                -q|--quiet)
 287                        GIT_QUIET=1
 288                        ;;
 289                --)
 290                        shift
 291                        break
 292                        ;;
 293                -*)
 294                        usage
 295                        ;;
 296                *)
 297                        break
 298                        ;;
 299                esac
 300                shift
 301        done
 302
 303        module_list "$@" |
 304        while read mode sha1 stage path
 305        do
 306                # Skip already registered paths
 307                name=$(module_name "$path") || exit
 308                url=$(git config submodule."$name".url)
 309                test -z "$url" || continue
 310
 311                url=$(git config -f .gitmodules submodule."$name".url)
 312                test -z "$url" &&
 313                die "No url found for submodule path '$path' in .gitmodules"
 314
 315                # Possibly a url relative to parent
 316                case "$url" in
 317                ./*|../*)
 318                        url=$(resolve_relative_url "$url") || exit
 319                        ;;
 320                esac
 321
 322                git config submodule."$name".url "$url" ||
 323                die "Failed to register url for submodule path '$path'"
 324
 325                upd="$(git config -f .gitmodules submodule."$name".update)"
 326                test -z "$upd" ||
 327                git config submodule."$name".update "$upd" ||
 328                die "Failed to register update mode for submodule path '$path'"
 329
 330                say "Submodule '$name' ($url) registered for path '$path'"
 331        done
 332}
 333
 334#
 335# Update each submodule path to correct revision, using clone and checkout as needed
 336#
 337# $@ = requested paths (default to all)
 338#
 339cmd_update()
 340{
 341        # parse $args after "submodule ... update".
 342        while test $# -ne 0
 343        do
 344                case "$1" in
 345                -q|--quiet)
 346                        shift
 347                        GIT_QUIET=1
 348                        ;;
 349                -i|--init)
 350                        init=1
 351                        shift
 352                        ;;
 353                -N|--no-fetch)
 354                        shift
 355                        nofetch=1
 356                        ;;
 357                -r|--rebase)
 358                        shift
 359                        update="rebase"
 360                        ;;
 361                --reference)
 362                        case "$2" in '') usage ;; esac
 363                        reference="--reference=$2"
 364                        shift 2
 365                        ;;
 366                --reference=*)
 367                        reference="$1"
 368                        shift
 369                        ;;
 370                -m|--merge)
 371                        shift
 372                        update="merge"
 373                        ;;
 374                --)
 375                        shift
 376                        break
 377                        ;;
 378                -*)
 379                        usage
 380                        ;;
 381                *)
 382                        break
 383                        ;;
 384                esac
 385        done
 386
 387        if test -n "$init"
 388        then
 389                cmd_init "--" "$@" || return
 390        fi
 391
 392        module_list "$@" |
 393        while read mode sha1 stage path
 394        do
 395                name=$(module_name "$path") || exit
 396                url=$(git config submodule."$name".url)
 397                update_module=$(git config submodule."$name".update)
 398                if test -z "$url"
 399                then
 400                        # Only mention uninitialized submodules when its
 401                        # path have been specified
 402                        test "$#" != "0" &&
 403                        say "Submodule path '$path' not initialized" &&
 404                        say "Maybe you want to use 'update --init'?"
 405                        continue
 406                fi
 407
 408                if ! test -d "$path"/.git -o -f "$path"/.git
 409                then
 410                        module_clone "$path" "$url" "$reference"|| exit
 411                        subsha1=
 412                else
 413                        subsha1=$(unset GIT_DIR; cd "$path" &&
 414                                git rev-parse --verify HEAD) ||
 415                        die "Unable to find current revision in submodule path '$path'"
 416                fi
 417
 418                if ! test -z "$update"
 419                then
 420                        update_module=$update
 421                fi
 422
 423                if test "$subsha1" != "$sha1"
 424                then
 425                        force=
 426                        if test -z "$subsha1"
 427                        then
 428                                force="-f"
 429                        fi
 430
 431                        if test -z "$nofetch"
 432                        then
 433                                (unset GIT_DIR; cd "$path" &&
 434                                        git-fetch) ||
 435                                die "Unable to fetch in submodule path '$path'"
 436                        fi
 437
 438                        case "$update_module" in
 439                        rebase)
 440                                command="git rebase"
 441                                action="rebase"
 442                                msg="rebased onto"
 443                                ;;
 444                        merge)
 445                                command="git merge"
 446                                action="merge"
 447                                msg="merged in"
 448                                ;;
 449                        *)
 450                                command="git checkout $force -q"
 451                                action="checkout"
 452                                msg="checked out"
 453                                ;;
 454                        esac
 455
 456                        (unset GIT_DIR; cd "$path" && $command "$sha1") ||
 457                        die "Unable to $action '$sha1' in submodule path '$path'"
 458                        say "Submodule path '$path': $msg '$sha1'"
 459                fi
 460        done
 461}
 462
 463set_name_rev () {
 464        revname=$( (
 465                unset GIT_DIR
 466                cd "$1" && {
 467                        git describe "$2" 2>/dev/null ||
 468                        git describe --tags "$2" 2>/dev/null ||
 469                        git describe --contains "$2" 2>/dev/null ||
 470                        git describe --all --always "$2"
 471                }
 472        ) )
 473        test -z "$revname" || revname=" ($revname)"
 474}
 475#
 476# Show commit summary for submodules in index or working tree
 477#
 478# If '--cached' is given, show summary between index and given commit,
 479# or between working tree and given commit
 480#
 481# $@ = [commit (default 'HEAD'),] requested paths (default all)
 482#
 483cmd_summary() {
 484        summary_limit=-1
 485        for_status=
 486
 487        # parse $args after "submodule ... summary".
 488        while test $# -ne 0
 489        do
 490                case "$1" in
 491                --cached)
 492                        cached="$1"
 493                        ;;
 494                --for-status)
 495                        for_status="$1"
 496                        ;;
 497                -n|--summary-limit)
 498                        if summary_limit=$(($2 + 0)) 2>/dev/null && test "$summary_limit" = "$2"
 499                        then
 500                                :
 501                        else
 502                                usage
 503                        fi
 504                        shift
 505                        ;;
 506                --)
 507                        shift
 508                        break
 509                        ;;
 510                -*)
 511                        usage
 512                        ;;
 513                *)
 514                        break
 515                        ;;
 516                esac
 517                shift
 518        done
 519
 520        test $summary_limit = 0 && return
 521
 522        if rev=$(git rev-parse -q --verify "$1^0")
 523        then
 524                head=$rev
 525                shift
 526        else
 527                head=HEAD
 528        fi
 529
 530        cd_to_toplevel
 531        # Get modified modules cared by user
 532        modules=$(git diff-index $cached --raw $head -- "$@" |
 533                egrep '^:([0-7]* )?160000' |
 534                while read mod_src mod_dst sha1_src sha1_dst status name
 535                do
 536                        # Always show modules deleted or type-changed (blob<->module)
 537                        test $status = D -o $status = T && echo "$name" && continue
 538                        # Also show added or modified modules which are checked out
 539                        GIT_DIR="$name/.git" git-rev-parse --git-dir >/dev/null 2>&1 &&
 540                        echo "$name"
 541                done
 542        )
 543
 544        test -z "$modules" && return
 545
 546        git diff-index $cached --raw $head -- $modules |
 547        egrep '^:([0-7]* )?160000' |
 548        cut -c2- |
 549        while read mod_src mod_dst sha1_src sha1_dst status name
 550        do
 551                if test -z "$cached" &&
 552                        test $sha1_dst = 0000000000000000000000000000000000000000
 553                then
 554                        case "$mod_dst" in
 555                        160000)
 556                                sha1_dst=$(GIT_DIR="$name/.git" git rev-parse HEAD)
 557                                ;;
 558                        100644 | 100755 | 120000)
 559                                sha1_dst=$(git hash-object $name)
 560                                ;;
 561                        000000)
 562                                ;; # removed
 563                        *)
 564                                # unexpected type
 565                                echo >&2 "unexpected mode $mod_dst"
 566                                continue ;;
 567                        esac
 568                fi
 569                missing_src=
 570                missing_dst=
 571
 572                test $mod_src = 160000 &&
 573                ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_src^0 >/dev/null &&
 574                missing_src=t
 575
 576                test $mod_dst = 160000 &&
 577                ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_dst^0 >/dev/null &&
 578                missing_dst=t
 579
 580                total_commits=
 581                case "$missing_src,$missing_dst" in
 582                t,)
 583                        errmsg="  Warn: $name doesn't contain commit $sha1_src"
 584                        ;;
 585                ,t)
 586                        errmsg="  Warn: $name doesn't contain commit $sha1_dst"
 587                        ;;
 588                t,t)
 589                        errmsg="  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
 590                        ;;
 591                *)
 592                        errmsg=
 593                        total_commits=$(
 594                        if test $mod_src = 160000 -a $mod_dst = 160000
 595                        then
 596                                range="$sha1_src...$sha1_dst"
 597                        elif test $mod_src = 160000
 598                        then
 599                                range=$sha1_src
 600                        else
 601                                range=$sha1_dst
 602                        fi
 603                        GIT_DIR="$name/.git" \
 604                        git log --pretty=oneline --first-parent $range | wc -l
 605                        )
 606                        total_commits=" ($(($total_commits + 0)))"
 607                        ;;
 608                esac
 609
 610                sha1_abbr_src=$(echo $sha1_src | cut -c1-7)
 611                sha1_abbr_dst=$(echo $sha1_dst | cut -c1-7)
 612                if test $status = T
 613                then
 614                        if test $mod_dst = 160000
 615                        then
 616                                echo "* $name $sha1_abbr_src(blob)->$sha1_abbr_dst(submodule)$total_commits:"
 617                        else
 618                                echo "* $name $sha1_abbr_src(submodule)->$sha1_abbr_dst(blob)$total_commits:"
 619                        fi
 620                else
 621                        echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
 622                fi
 623                if test -n "$errmsg"
 624                then
 625                        # Don't give error msg for modification whose dst is not submodule
 626                        # i.e. deleted or changed to blob
 627                        test $mod_dst = 160000 && echo "$errmsg"
 628                else
 629                        if test $mod_src = 160000 -a $mod_dst = 160000
 630                        then
 631                                limit=
 632                                test $summary_limit -gt 0 && limit="-$summary_limit"
 633                                GIT_DIR="$name/.git" \
 634                                git log $limit --pretty='format:  %m %s' \
 635                                --first-parent $sha1_src...$sha1_dst
 636                        elif test $mod_dst = 160000
 637                        then
 638                                GIT_DIR="$name/.git" \
 639                                git log --pretty='format:  > %s' -1 $sha1_dst
 640                        else
 641                                GIT_DIR="$name/.git" \
 642                                git log --pretty='format:  < %s' -1 $sha1_src
 643                        fi
 644                        echo
 645                fi
 646                echo
 647        done |
 648        if test -n "$for_status"; then
 649                echo "# Modified submodules:"
 650                echo "#"
 651                sed -e 's|^|# |' -e 's|^# $|#|'
 652        else
 653                cat
 654        fi
 655}
 656#
 657# List all submodules, prefixed with:
 658#  - submodule not initialized
 659#  + different revision checked out
 660#
 661# If --cached was specified the revision in the index will be printed
 662# instead of the currently checked out revision.
 663#
 664# $@ = requested paths (default to all)
 665#
 666cmd_status()
 667{
 668        # parse $args after "submodule ... status".
 669        while test $# -ne 0
 670        do
 671                case "$1" in
 672                -q|--quiet)
 673                        GIT_QUIET=1
 674                        ;;
 675                --cached)
 676                        cached=1
 677                        ;;
 678                --)
 679                        shift
 680                        break
 681                        ;;
 682                -*)
 683                        usage
 684                        ;;
 685                *)
 686                        break
 687                        ;;
 688                esac
 689                shift
 690        done
 691
 692        module_list "$@" |
 693        while read mode sha1 stage path
 694        do
 695                name=$(module_name "$path") || exit
 696                url=$(git config submodule."$name".url)
 697                if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git
 698                then
 699                        say "-$sha1 $path"
 700                        continue;
 701                fi
 702                set_name_rev "$path" "$sha1"
 703                if git diff-files --quiet -- "$path"
 704                then
 705                        say " $sha1 $path$revname"
 706                else
 707                        if test -z "$cached"
 708                        then
 709                                sha1=$(unset GIT_DIR; cd "$path" && git rev-parse --verify HEAD)
 710                                set_name_rev "$path" "$sha1"
 711                        fi
 712                        say "+$sha1 $path$revname"
 713                fi
 714        done
 715}
 716#
 717# Sync remote urls for submodules
 718# This makes the value for remote.$remote.url match the value
 719# specified in .gitmodules.
 720#
 721cmd_sync()
 722{
 723        while test $# -ne 0
 724        do
 725                case "$1" in
 726                -q|--quiet)
 727                        GIT_QUIET=1
 728                        shift
 729                        ;;
 730                --)
 731                        shift
 732                        break
 733                        ;;
 734                -*)
 735                        usage
 736                        ;;
 737                *)
 738                        break
 739                        ;;
 740                esac
 741        done
 742        cd_to_toplevel
 743        module_list "$@" |
 744        while read mode sha1 stage path
 745        do
 746                name=$(module_name "$path")
 747                url=$(git config -f .gitmodules --get submodule."$name".url)
 748
 749                # Possibly a url relative to parent
 750                case "$url" in
 751                ./*|../*)
 752                        url=$(resolve_relative_url "$url") || exit
 753                        ;;
 754                esac
 755
 756                if test -e "$path"/.git
 757                then
 758                (
 759                        unset GIT_DIR
 760                        cd "$path"
 761                        remote=$(get_default_remote)
 762                        say "Synchronizing submodule url for '$name'"
 763                        git config remote."$remote".url "$url"
 764                )
 765                fi
 766        done
 767}
 768
 769# This loop parses the command line arguments to find the
 770# subcommand name to dispatch.  Parsing of the subcommand specific
 771# options are primarily done by the subcommand implementations.
 772# Subcommand specific options such as --branch and --cached are
 773# parsed here as well, for backward compatibility.
 774
 775while test $# != 0 && test -z "$command"
 776do
 777        case "$1" in
 778        add | foreach | init | update | status | summary | sync)
 779                command=$1
 780                ;;
 781        -q|--quiet)
 782                GIT_QUIET=1
 783                ;;
 784        -b|--branch)
 785                case "$2" in
 786                '')
 787                        usage
 788                        ;;
 789                esac
 790                branch="$2"; shift
 791                ;;
 792        --cached)
 793                cached="$1"
 794                ;;
 795        --)
 796                break
 797                ;;
 798        -*)
 799                usage
 800                ;;
 801        *)
 802                break
 803                ;;
 804        esac
 805        shift
 806done
 807
 808# No command word defaults to "status"
 809test -n "$command" || command=status
 810
 811# "-b branch" is accepted only by "add"
 812if test -n "$branch" && test "$command" != add
 813then
 814        usage
 815fi
 816
 817# "--cached" is accepted only by "status" and "summary"
 818if test -n "$cached" && test "$command" != status -a "$command" != summary
 819then
 820        usage
 821fi
 822
 823"cmd_$command" "$@"