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