git-submodule.shon commit gitweb: unify boolean feature subroutines (cdad817)
   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        # strip trailing slashes from path
 170        path=$(echo "$path" | sed -e 's|/*$||')
 171
 172        git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
 173        die "'$path' already exists in the index"
 174
 175        # perhaps the path exists and is already a git repo, else clone it
 176        if test -e "$path"
 177        then
 178                if test -d "$path"/.git -o -f "$path"/.git
 179                then
 180                        echo "Adding existing repo at '$path' to the index"
 181                else
 182                        die "'$path' already exists and is not a valid git repo"
 183                fi
 184
 185                case "$repo" in
 186                ./*|../*)
 187                        url=$(resolve_relative_url "$repo") || exit
 188                    ;;
 189                *)
 190                        url="$repo"
 191                        ;;
 192                esac
 193                git config submodule."$path".url "$url"
 194        else
 195
 196                module_clone "$path" "$realrepo" || exit
 197                (unset GIT_DIR; cd "$path" && git checkout -f -q ${branch:+-b "$branch" "origin/$branch"}) ||
 198                die "Unable to checkout submodule '$path'"
 199        fi
 200
 201        git add "$path" ||
 202        die "Failed to add submodule '$path'"
 203
 204        git config -f .gitmodules submodule."$path".path "$path" &&
 205        git config -f .gitmodules submodule."$path".url "$repo" &&
 206        git add .gitmodules ||
 207        die "Failed to register submodule '$path'"
 208}
 209
 210#
 211# Execute an arbitrary command sequence in each checked out
 212# submodule
 213#
 214# $@ = command to execute
 215#
 216cmd_foreach()
 217{
 218        module_list |
 219        while read mode sha1 stage path
 220        do
 221                if test -e "$path"/.git
 222                then
 223                        say "Entering '$path'"
 224                        (cd "$path" && eval "$@") ||
 225                        die "Stopping at '$path'; script returned non-zero status."
 226                fi
 227        done
 228}
 229
 230#
 231# Register submodules in .git/config
 232#
 233# $@ = requested paths (default to all)
 234#
 235cmd_init()
 236{
 237        # parse $args after "submodule ... init".
 238        while test $# -ne 0
 239        do
 240                case "$1" in
 241                -q|--quiet)
 242                        quiet=1
 243                        ;;
 244                --)
 245                        shift
 246                        break
 247                        ;;
 248                -*)
 249                        usage
 250                        ;;
 251                *)
 252                        break
 253                        ;;
 254                esac
 255                shift
 256        done
 257
 258        module_list "$@" |
 259        while read mode sha1 stage path
 260        do
 261                # Skip already registered paths
 262                name=$(module_name "$path") || exit
 263                url=$(git config submodule."$name".url)
 264                test -z "$url" || continue
 265
 266                url=$(git config -f .gitmodules submodule."$name".url)
 267                test -z "$url" &&
 268                die "No url found for submodule path '$path' in .gitmodules"
 269
 270                # Possibly a url relative to parent
 271                case "$url" in
 272                ./*|../*)
 273                        url=$(resolve_relative_url "$url") || exit
 274                        ;;
 275                esac
 276
 277                git config submodule."$name".url "$url" ||
 278                die "Failed to register url for submodule path '$path'"
 279
 280                say "Submodule '$name' ($url) registered for path '$path'"
 281        done
 282}
 283
 284#
 285# Update each submodule path to correct revision, using clone and checkout as needed
 286#
 287# $@ = requested paths (default to all)
 288#
 289cmd_update()
 290{
 291        # parse $args after "submodule ... update".
 292        while test $# -ne 0
 293        do
 294                case "$1" in
 295                -q|--quiet)
 296                        shift
 297                        quiet=1
 298                        ;;
 299                -i|--init)
 300                        shift
 301                        cmd_init "$@" || return
 302                        ;;
 303                --)
 304                        shift
 305                        break
 306                        ;;
 307                -*)
 308                        usage
 309                        ;;
 310                *)
 311                        break
 312                        ;;
 313                esac
 314        done
 315
 316        module_list "$@" |
 317        while read mode sha1 stage path
 318        do
 319                name=$(module_name "$path") || exit
 320                url=$(git config submodule."$name".url)
 321                if test -z "$url"
 322                then
 323                        # Only mention uninitialized submodules when its
 324                        # path have been specified
 325                        test "$#" != "0" &&
 326                        say "Submodule path '$path' not initialized" &&
 327                        say "Maybe you want to use 'update --init'?"
 328                        continue
 329                fi
 330
 331                if ! test -d "$path"/.git -o -f "$path"/.git
 332                then
 333                        module_clone "$path" "$url" || exit
 334                        subsha1=
 335                else
 336                        subsha1=$(unset GIT_DIR; cd "$path" &&
 337                                git rev-parse --verify HEAD) ||
 338                        die "Unable to find current revision in submodule path '$path'"
 339                fi
 340
 341                if test "$subsha1" != "$sha1"
 342                then
 343                        force=
 344                        if test -z "$subsha1"
 345                        then
 346                                force="-f"
 347                        fi
 348                        (unset GIT_DIR; cd "$path" && git-fetch &&
 349                                git-checkout $force -q "$sha1") ||
 350                        die "Unable to checkout '$sha1' in submodule path '$path'"
 351
 352                        say "Submodule path '$path': checked out '$sha1'"
 353                fi
 354        done
 355}
 356
 357set_name_rev () {
 358        revname=$( (
 359                unset GIT_DIR
 360                cd "$1" && {
 361                        git describe "$2" 2>/dev/null ||
 362                        git describe --tags "$2" 2>/dev/null ||
 363                        git describe --contains "$2" 2>/dev/null ||
 364                        git describe --all --always "$2"
 365                }
 366        ) )
 367        test -z "$revname" || revname=" ($revname)"
 368}
 369#
 370# Show commit summary for submodules in index or working tree
 371#
 372# If '--cached' is given, show summary between index and given commit,
 373# or between working tree and given commit
 374#
 375# $@ = [commit (default 'HEAD'),] requested paths (default all)
 376#
 377cmd_summary() {
 378        summary_limit=-1
 379        for_status=
 380
 381        # parse $args after "submodule ... summary".
 382        while test $# -ne 0
 383        do
 384                case "$1" in
 385                --cached)
 386                        cached="$1"
 387                        ;;
 388                --for-status)
 389                        for_status="$1"
 390                        ;;
 391                -n|--summary-limit)
 392                        if summary_limit=$(($2 + 0)) 2>/dev/null && test "$summary_limit" = "$2"
 393                        then
 394                                :
 395                        else
 396                                usage
 397                        fi
 398                        shift
 399                        ;;
 400                --)
 401                        shift
 402                        break
 403                        ;;
 404                -*)
 405                        usage
 406                        ;;
 407                *)
 408                        break
 409                        ;;
 410                esac
 411                shift
 412        done
 413
 414        test $summary_limit = 0 && return
 415
 416        if rev=$(git rev-parse -q --verify "$1^0")
 417        then
 418                head=$rev
 419                shift
 420        else
 421                head=HEAD
 422        fi
 423
 424        cd_to_toplevel
 425        # Get modified modules cared by user
 426        modules=$(git diff-index $cached --raw $head -- "$@" |
 427                egrep '^:([0-7]* )?160000' |
 428                while read mod_src mod_dst sha1_src sha1_dst status name
 429                do
 430                        # Always show modules deleted or type-changed (blob<->module)
 431                        test $status = D -o $status = T && echo "$name" && continue
 432                        # Also show added or modified modules which are checked out
 433                        GIT_DIR="$name/.git" git-rev-parse --git-dir >/dev/null 2>&1 &&
 434                        echo "$name"
 435                done
 436        )
 437
 438        test -z "$modules" && return
 439
 440        git diff-index $cached --raw $head -- $modules |
 441        egrep '^:([0-7]* )?160000' |
 442        cut -c2- |
 443        while read mod_src mod_dst sha1_src sha1_dst status name
 444        do
 445                if test -z "$cached" &&
 446                        test $sha1_dst = 0000000000000000000000000000000000000000
 447                then
 448                        case "$mod_dst" in
 449                        160000)
 450                                sha1_dst=$(GIT_DIR="$name/.git" git rev-parse HEAD)
 451                                ;;
 452                        100644 | 100755 | 120000)
 453                                sha1_dst=$(git hash-object $name)
 454                                ;;
 455                        000000)
 456                                ;; # removed
 457                        *)
 458                                # unexpected type
 459                                echo >&2 "unexpected mode $mod_dst"
 460                                continue ;;
 461                        esac
 462                fi
 463                missing_src=
 464                missing_dst=
 465
 466                test $mod_src = 160000 &&
 467                ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_src^0 >/dev/null &&
 468                missing_src=t
 469
 470                test $mod_dst = 160000 &&
 471                ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_dst^0 >/dev/null &&
 472                missing_dst=t
 473
 474                total_commits=
 475                case "$missing_src,$missing_dst" in
 476                t,)
 477                        errmsg="  Warn: $name doesn't contain commit $sha1_src"
 478                        ;;
 479                ,t)
 480                        errmsg="  Warn: $name doesn't contain commit $sha1_dst"
 481                        ;;
 482                t,t)
 483                        errmsg="  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
 484                        ;;
 485                *)
 486                        errmsg=
 487                        total_commits=$(
 488                        if test $mod_src = 160000 -a $mod_dst = 160000
 489                        then
 490                                range="$sha1_src...$sha1_dst"
 491                        elif test $mod_src = 160000
 492                        then
 493                                range=$sha1_src
 494                        else
 495                                range=$sha1_dst
 496                        fi
 497                        GIT_DIR="$name/.git" \
 498                        git log --pretty=oneline --first-parent $range | wc -l
 499                        )
 500                        total_commits=" ($(($total_commits + 0)))"
 501                        ;;
 502                esac
 503
 504                sha1_abbr_src=$(echo $sha1_src | cut -c1-7)
 505                sha1_abbr_dst=$(echo $sha1_dst | cut -c1-7)
 506                if test $status = T
 507                then
 508                        if test $mod_dst = 160000
 509                        then
 510                                echo "* $name $sha1_abbr_src(blob)->$sha1_abbr_dst(submodule)$total_commits:"
 511                        else
 512                                echo "* $name $sha1_abbr_src(submodule)->$sha1_abbr_dst(blob)$total_commits:"
 513                        fi
 514                else
 515                        echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
 516                fi
 517                if test -n "$errmsg"
 518                then
 519                        # Don't give error msg for modification whose dst is not submodule
 520                        # i.e. deleted or changed to blob
 521                        test $mod_dst = 160000 && echo "$errmsg"
 522                else
 523                        if test $mod_src = 160000 -a $mod_dst = 160000
 524                        then
 525                                limit=
 526                                test $summary_limit -gt 0 && limit="-$summary_limit"
 527                                GIT_DIR="$name/.git" \
 528                                git log $limit --pretty='format:  %m %s' \
 529                                --first-parent $sha1_src...$sha1_dst
 530                        elif test $mod_dst = 160000
 531                        then
 532                                GIT_DIR="$name/.git" \
 533                                git log --pretty='format:  > %s' -1 $sha1_dst
 534                        else
 535                                GIT_DIR="$name/.git" \
 536                                git log --pretty='format:  < %s' -1 $sha1_src
 537                        fi
 538                        echo
 539                fi
 540                echo
 541        done |
 542        if test -n "$for_status"; then
 543                echo "# Modified submodules:"
 544                echo "#"
 545                sed -e 's|^|# |' -e 's|^# $|#|'
 546        else
 547                cat
 548        fi
 549}
 550#
 551# List all submodules, prefixed with:
 552#  - submodule not initialized
 553#  + different revision checked out
 554#
 555# If --cached was specified the revision in the index will be printed
 556# instead of the currently checked out revision.
 557#
 558# $@ = requested paths (default to all)
 559#
 560cmd_status()
 561{
 562        # parse $args after "submodule ... status".
 563        while test $# -ne 0
 564        do
 565                case "$1" in
 566                -q|--quiet)
 567                        quiet=1
 568                        ;;
 569                --cached)
 570                        cached=1
 571                        ;;
 572                --)
 573                        shift
 574                        break
 575                        ;;
 576                -*)
 577                        usage
 578                        ;;
 579                *)
 580                        break
 581                        ;;
 582                esac
 583                shift
 584        done
 585
 586        module_list "$@" |
 587        while read mode sha1 stage path
 588        do
 589                name=$(module_name "$path") || exit
 590                url=$(git config submodule."$name".url)
 591                if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git
 592                then
 593                        say "-$sha1 $path"
 594                        continue;
 595                fi
 596                set_name_rev "$path" "$sha1"
 597                if git diff-files --quiet -- "$path"
 598                then
 599                        say " $sha1 $path$revname"
 600                else
 601                        if test -z "$cached"
 602                        then
 603                                sha1=$(unset GIT_DIR; cd "$path" && git rev-parse --verify HEAD)
 604                                set_name_rev "$path" "$sha1"
 605                        fi
 606                        say "+$sha1 $path$revname"
 607                fi
 608        done
 609}
 610#
 611# Sync remote urls for submodules
 612# This makes the value for remote.$remote.url match the value
 613# specified in .gitmodules.
 614#
 615cmd_sync()
 616{
 617        while test $# -ne 0
 618        do
 619                case "$1" in
 620                -q|--quiet)
 621                        quiet=1
 622                        shift
 623                        ;;
 624                --)
 625                        shift
 626                        break
 627                        ;;
 628                -*)
 629                        usage
 630                        ;;
 631                *)
 632                        break
 633                        ;;
 634                esac
 635        done
 636        cd_to_toplevel
 637        module_list "$@" |
 638        while read mode sha1 stage path
 639        do
 640                name=$(module_name "$path")
 641                url=$(git config -f .gitmodules --get submodule."$name".url)
 642
 643                # Possibly a url relative to parent
 644                case "$url" in
 645                ./*|../*)
 646                        url=$(resolve_relative_url "$url") || exit
 647                        ;;
 648                esac
 649
 650                if test -e "$path"/.git
 651                then
 652                (
 653                        unset GIT_DIR
 654                        cd "$path"
 655                        remote=$(get_default_remote)
 656                        say "Synchronizing submodule url for '$name'"
 657                        git config remote."$remote".url "$url"
 658                )
 659                fi
 660        done
 661}
 662
 663# This loop parses the command line arguments to find the
 664# subcommand name to dispatch.  Parsing of the subcommand specific
 665# options are primarily done by the subcommand implementations.
 666# Subcommand specific options such as --branch and --cached are
 667# parsed here as well, for backward compatibility.
 668
 669while test $# != 0 && test -z "$command"
 670do
 671        case "$1" in
 672        add | foreach | init | update | status | summary | sync)
 673                command=$1
 674                ;;
 675        -q|--quiet)
 676                quiet=1
 677                ;;
 678        -b|--branch)
 679                case "$2" in
 680                '')
 681                        usage
 682                        ;;
 683                esac
 684                branch="$2"; shift
 685                ;;
 686        --cached)
 687                cached="$1"
 688                ;;
 689        --)
 690                break
 691                ;;
 692        -*)
 693                usage
 694                ;;
 695        *)
 696                break
 697                ;;
 698        esac
 699        shift
 700done
 701
 702# No command word defaults to "status"
 703test -n "$command" || command=status
 704
 705# "-b branch" is accepted only by "add"
 706if test -n "$branch" && test "$command" != add
 707then
 708        usage
 709fi
 710
 711# "--cached" is accepted only by "status" and "summary"
 712if test -n "$cached" && test "$command" != status -a "$command" != summary
 713then
 714        usage
 715fi
 716
 717"cmd_$command" "$@"