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