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