#!/bin/sh # # git-submodule.sh: add, init, update or list git submodules # # Copyright (c) 2007 Lars Hjemli setglobal dashless = $[basename $0 | sed -e 's/-/ /] setglobal USAGE = ""[--quiet] add [-b ] [-f|--force] [--name ] [--reference ] [--] [] or: $dashless [--quiet] status [--cached] [--recursive] [--] [...] or: $dashless [--quiet] init [--] [...] or: $dashless [--quiet] deinit [-f|--force] (--all| [--] ...) or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-shallow] [--reference ] [--recursive] [--] [...] or: $dashless [--quiet] summary [--cached|--files] [--summary-limit ] [commit] [--] [...] or: $dashless [--quiet] foreach [--recursive] or: $dashless [--quiet] sync [--recursive] [--] [...]"" setglobal OPTIONS_SPEC = '' setglobal SUBDIRECTORY_OK = 'Yes' source git-sh-setup source git-parse-remote require_work_tree setglobal wt_prefix = $[git rev-parse --show-prefix] cd_to_toplevel # Restrict ourselves to a vanilla subset of protocols; the URLs # we get are under control of a remote repository, and we do not # want them kicking off arbitrary git-remote-* programs. # # If the user has already specified a set of allowed protocols, # we assume they know what they're doing and use that instead. : $(GIT_ALLOW_PROTOCOL=file:git:http:https:ssh) export GIT_ALLOW_PROTOCOL setglobal command = '' setglobal branch = '' setglobal force = '' setglobal reference = '' setglobal cached = '' setglobal recursive = '' setglobal init = '' setglobal files = '' setglobal remote = '' setglobal nofetch = '' setglobal update = '' setglobal prefix = '' setglobal custom_name = '' setglobal depth = '' setglobal progress = '' proc die_if_unmatched { if test $1 = "#unmatched" { exit ${2:-1} } } # # Print a submodule configuration setting # # $1 = submodule name # $2 = option name # $3 = default value # # Checks in the usual git-config places first (for overrides), # otherwise it falls back on .gitmodules. This allows you to # distribute project-wide defaults in .gitmodules, while still # customizing individual repositories if necessary. If the option is # not in .gitmodules either, print a default value. # proc get_submodule_config { setglobal name = $1 setglobal option = $2 setglobal default = $3 setglobal value = $[git config submodule."$name"."$option] if test -z $value { setglobal value = $[git config -f .gitmodules submodule."$name"."$option] } printf '%s' $(value:-$default) } proc isnumber { setglobal n = $shExpr('$1 + 0') 2>/dev/null && test $n = $1 } # Sanitize the local git environment for use within a submodule. We # can't simply use clear_local_git_env since we want to preserve some # of the settings from GIT_CONFIG_PARAMETERS. proc sanitize_submodule_env { setglobal save_config = $GIT_CONFIG_PARAMETERS clear_local_git_env setglobal GIT_CONFIG_PARAMETERS = $save_config export GIT_CONFIG_PARAMETERS } # # Add a new submodule to the working tree, .gitmodules and the index # # $@ = repo path # # optional branch is stored in global branch variable # proc cmd_add { # parse $args after "submodule ... add". setglobal reference_path = '' while test $# -ne 0 { match $1 { with -b | --branch match $2 { with '' usage } setglobal branch = $2 shift with -f | --force setglobal force = $1 with -q|--quiet setglobal GIT_QUIET = '1' with --reference match $2 { with '' usage } setglobal reference_path = $2 shift with --reference=* setglobal reference_path = $(1#--reference=) with --name match $2 { with '' usage } setglobal custom_name = $2 shift with --depth match $2 { with '' usage } setglobal depth = ""--depth=$2"" shift with --depth=* setglobal depth = $1 with -- shift break with -* usage with * break } shift } if test -n $reference_path { is_absolute_path $reference_path || setglobal reference_path = ""$wt_prefix$reference_path"" setglobal reference = ""--reference=$reference_path"" } setglobal repo = $1 setglobal sm_path = $2 if test -z $sm_path { setglobal sm_path = $[printf '%s\n' $repo | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g] } if test -z $repo || test -z $sm_path { usage } is_absolute_path $sm_path || setglobal sm_path = ""$wt_prefix$sm_path"" # assure repo is absolute or relative to parent match $repo { with ./*|../* test -z $wt_prefix || die $[gettext "Relative path can only be used from the toplevel of the working tree] # dereference source url relative to parent's url setglobal realrepo = $[git submodule--helper resolve-relative-url $repo] || exit with *:*|/* # absolute url setglobal realrepo = $repo with * die $[eval_gettext "repo URL: '\$repo' must be absolute or begin with ./|../] } # normalize path: # multiple //; leading ./; /./; /../; trailing / setglobal sm_path = $[printf '%s/\n' $sm_path | sed -e ' s|//*|/|g s|^\(\./\)*|| s|/\(\./\)*|/|g :start s|\([^/]*\)/\.\./|| tstart s|/*$|| ] git ls-files --error-unmatch $sm_path > /dev/null 2>&1 && die $[eval_gettext "'\$sm_path' already exists in the index] if test -z $force && ! git add --dry-run --ignore-missing $sm_path > /dev/null 2>&1 { eval_gettextln "The following path is ignored by one of your .gitignore files: \$sm_path Use -f if you really want to add it." >&2 exit 1 } if test -n $custom_name { setglobal sm_name = $custom_name } else { setglobal sm_name = $sm_path } # perhaps the path exists and is already a git repo, else clone it if test -e $sm_path { if test -d "$sm_path"/.git || test -f "$sm_path"/.git { eval_gettextln "Adding existing repo at '\$sm_path' to the index" } else { die $[eval_gettext "'\$sm_path' already exists and is not a valid git repo] } } else { if test -d ".git/modules/$sm_name" { if test -z $force { eval_gettextln >&2 "A git directory for '\$sm_name' is found locally with remote(s):" env GIT_DIR=".git/modules/$sm_name" GIT_WORK_TREE=. git remote -v | grep '(fetch)' | sed -e s,^," ", -e s,' (fetch)',, >&2 die $[eval_gettextln "\ If you want to reuse this local git directory instead of cloning again from \$realrepo use the '--force' option. If the local git directory is not the correct repo or you are unsure what this means choose another name with the '--name' option.] } else { eval_gettextln "Reactivating local git directory for submodule '\$sm_name'." } } git submodule--helper clone $(GIT_QUIET:+--quiet) --prefix $wt_prefix --path $sm_path --name $sm_name --url $realrepo $(reference:+"$reference") $(depth:+"$depth") || exit shell { sanitize_submodule_env cd $sm_path && # ash fails to wordsplit ${branch:+-b "$branch"...} match $branch { with '' git checkout -f -q with ?* git checkout -f -q -B $branch "origin/$branch" } } || die $[eval_gettext "Unable to checkout submodule '\$sm_path'] } git config submodule."$sm_name".url $realrepo git add $force $sm_path || die $[eval_gettext "Failed to add submodule '\$sm_path'] git config -f .gitmodules submodule."$sm_name".path $sm_path && git config -f .gitmodules submodule."$sm_name".url $repo && if test -n $branch { git config -f .gitmodules submodule."$sm_name".branch $branch } && git add --force .gitmodules || die $[eval_gettext "Failed to register submodule '\$sm_path'] } # # Execute an arbitrary command sequence in each checked out # submodule # # $@ = command to execute # proc cmd_foreach { # parse $args after "submodule ... foreach". while test $# -ne 0 { match $1 { with -q|--quiet setglobal GIT_QUIET = '1' with --recursive setglobal recursive = '1' with -* usage with * break } shift } setglobal toplevel = $[pwd] # dup stdin so that it can be restored when running the external # command in the subshell (and a recursive call to this function) exec 3<&0 do { git submodule--helper list --prefix $wt_prefix || echo "#unmatched" $Status } | while read mode sha1 stage sm_path { die_if_unmatched $mode $sha1 if test -e "$sm_path"/.git { setglobal displaypath = $[git submodule--helper relative-path "$prefix$sm_path" $wt_prefix] say $[eval_gettext "Entering '\$displaypath'] setglobal name = $[git submodule--helper name $sm_path] shell { setglobal prefix = ""$prefix$sm_path/"" sanitize_submodule_env cd $sm_path && setglobal sm_path = $[git submodule--helper relative-path $sm_path $wt_prefix] && # we make $path available to scripts ... setglobal path = $sm_path && if test $Argc -eq 1 { eval $1 } else { @ARGV } && if test -n $recursive { cmd_foreach "--recursive" @ARGV } } <&3 3<&- || die $[eval_gettext "Stopping at '\$displaypath'; script returned non-zero status.] } } } # # Register submodules in .git/config # # $@ = requested paths (default to all) # proc cmd_init { # parse $args after "submodule ... init". while test $# -ne 0 { match $1 { with -q|--quiet setglobal GIT_QUIET = '1' with -- shift break with -* usage with * break } shift } git $(wt_prefix:+-C "$wt_prefix") submodule--helper init $(GIT_QUIET:+--quiet) $(prefix:+--prefix "$prefix") @ARGV } # # Unregister submodules from .git/config and remove their work tree # proc cmd_deinit { # parse $args after "submodule ... deinit". setglobal deinit_all = '' while test $# -ne 0 { match $1 { with -f|--force setglobal force = $1 with -q|--quiet setglobal GIT_QUIET = '1' with --all setglobal deinit_all = 't' with -- shift break with -* usage with * break } shift } if test -n $deinit_all && test "$Argc" -ne 0 { echo >&2 $[eval_gettext "pathspec and --all are incompatible] usage } if test $Argc = 0 && test -z $deinit_all { die $[eval_gettext "Use '--all' if you really want to deinitialize all submodules] } do { git submodule--helper list --prefix $wt_prefix @ARGV || echo "#unmatched" $Status } | while read mode sha1 stage sm_path { die_if_unmatched $mode $sha1 setglobal name = $[git submodule--helper name $sm_path] || exit setglobal displaypath = $[git submodule--helper relative-path $sm_path $wt_prefix] # Remove the submodule work tree (unless the user already did it) if test -d $sm_path { # Protect submodules containing a .git directory if test -d "$sm_path/.git" { die $[eval_gettext "\ Submodule work tree '\$displaypath' contains a .git directory (use 'rm -rf' if you really want to remove it including all of its history)] } if test -z $force { git rm -qn $sm_path || die $[eval_gettext "Submodule work tree '\$displaypath' contains local modifications; use '-f' to discard them] } rm -rf $sm_path && say $[eval_gettext "Cleared directory '\$displaypath'] || say $[eval_gettext "Could not remove submodule work tree '\$displaypath'] } mkdir $sm_path || say $[eval_gettext "Could not create empty submodule directory '\$displaypath'] # Remove the .git/config entries (unless the user already did it) if test -n $[git config --get-regexp submodule."$name\.] { # Remove the whole section so we have a clean state when # the user later decides to init this submodule again setglobal url = $[git config submodule."$name".url] git config --remove-section submodule."$name" 2>/dev/null && say $[eval_gettext "Submodule '\$name' (\$url) unregistered for path '\$displaypath'] } } } proc is_tip_reachable ( sanitize_submodule_env && cd "$1" && rev=$(git rev-list -n 1 "$2" --not --all 2>/dev/null) && test -z "$rev" ) proc fetch_in_submodule ( sanitize_submodule_env && cd "$1" && case "$2" in '') git fetch ;; *) shift git fetch $(get_default_remote) "$@" ;; esac ) # # Update each submodule path to correct revision, using clone and checkout as needed # # $@ = requested paths (default to all) # proc cmd_update { # parse $args after "submodule ... update". while test $# -ne 0 { match $1 { with -q|--quiet setglobal GIT_QUIET = '1' with --progress setglobal progress = '"--progress'" with -i|--init setglobal init = '1' with --remote setglobal remote = '1' with -N|--no-fetch setglobal nofetch = '1' with -f|--force setglobal force = $1 with -r|--rebase setglobal update = '"rebase'" with --reference match $2 { with '' usage } setglobal reference = ""--reference=$2"" shift with --reference=* setglobal reference = $1 with -m|--merge setglobal update = '"merge'" with --recursive setglobal recursive = '1' with --checkout setglobal update = '"checkout'" with --recommend-shallow setglobal recommend_shallow = '"--recommend-shallow'" with --no-recommend-shallow setglobal recommend_shallow = '"--no-recommend-shallow'" with --depth match $2 { with '' usage } setglobal depth = ""--depth=$2"" shift with --depth=* setglobal depth = $1 with -j|--jobs match $2 { with '' usage } setglobal jobs = ""--jobs=$2"" shift with --jobs=* setglobal jobs = $1 with -- shift break with -* usage with * break } shift } if test -n $init { cmd_init "--" @ARGV || return } do { git submodule--helper update-clone $(GIT_QUIET:+--quiet) \ $(progress:+"$progress") \ $(wt_prefix:+--prefix "$wt_prefix") \ $(prefix:+--recursive-prefix "$prefix") \ $(update:+--update "$update") \ $(reference:+"$reference") \ $(depth:+--depth "$depth") \ $(recommend_shallow:+"$recommend_shallow") \ $(jobs:+$jobs) \ @ARGV || echo "#unmatched" $Status } | do { setglobal err = '' while read mode sha1 stage just_cloned sm_path { die_if_unmatched $mode $sha1 setglobal name = $[git submodule--helper name $sm_path] || exit setglobal url = $[git config submodule."$name".url] if ! test -z $update { setglobal update_module = $update } else { setglobal update_module = $[git config submodule."$name".update] if test -z $update_module { setglobal update_module = '"checkout'" } } setglobal displaypath = $[git submodule--helper relative-path "$prefix$sm_path" $wt_prefix] if test $just_cloned -eq 1 { setglobal subsha1 = '' setglobal update_module = 'checkout' } else { setglobal subsha1 = $[sanitize_submodule_env; cd $sm_path && git rev-parse --verify HEAD] || die $[eval_gettext "Unable to find current revision in submodule path '\$displaypath'] } if test -n $remote { setglobal branch = $[git submodule--helper remote-branch $sm_path] if test -z $nofetch { # Fetch remote before determining tracking $sha1 fetch_in_submodule $sm_path $depth || die $[eval_gettext "Unable to fetch in submodule path '\$sm_path'] } setglobal remote_name = $[sanitize_submodule_env; cd $sm_path && get_default_remote] setglobal sha1 = $[sanitize_submodule_env; cd $sm_path && git rev-parse --verify "$(remote_name)/$(branch)] || die $[eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'] } if test $subsha1 != $sha1 || test -n $force { setglobal subforce = $force # If we don't already have a -f flag and the submodule has never been checked out if test -z $subsha1 && test -z $force { setglobal subforce = '"-f'" } if test -z $nofetch { # Run fetch only if $sha1 isn't present or it # is not reachable from a ref. is_tip_reachable $sm_path $sha1 || fetch_in_submodule $sm_path $depth || die $[eval_gettext "Unable to fetch in submodule path '\$displaypath'] # Now we tried the usual fetch, but $sha1 may # not be reachable from any of the refs is_tip_reachable $sm_path $sha1 || fetch_in_submodule $sm_path $depth $sha1 || die $[eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain \$sha1. Direct fetching of that commit failed.] } setglobal must_die_on_failure = '' match $update_module { with checkout setglobal command = ""git checkout $subforce -q"" setglobal die_msg = $[eval_gettext "Unable to checkout '\$sha1' in submodule path '\$displaypath'] setglobal say_msg = $[eval_gettext "Submodule path '\$displaypath': checked out '\$sha1'] with rebase setglobal command = '"git rebase'" setglobal die_msg = $[eval_gettext "Unable to rebase '\$sha1' in submodule path '\$displaypath'] setglobal say_msg = $[eval_gettext "Submodule path '\$displaypath': rebased into '\$sha1'] setglobal must_die_on_failure = 'yes' with merge setglobal command = '"git merge'" setglobal die_msg = $[eval_gettext "Unable to merge '\$sha1' in submodule path '\$displaypath'] setglobal say_msg = $[eval_gettext "Submodule path '\$displaypath': merged in '\$sha1'] setglobal must_die_on_failure = 'yes' with !* setglobal command = $(update_module#!) setglobal die_msg = $[eval_gettext "Execution of '\$command \$sha1' failed in submodule path '\$displaypath'] setglobal say_msg = $[eval_gettext "Submodule path '\$displaypath': '\$command \$sha1'] setglobal must_die_on_failure = 'yes' with * die $[eval_gettext "Invalid update mode '$update_module' for submodule '$name'] } if shell {sanitize_submodule_env; cd $sm_path && $command $sha1} { say $say_msg } elif test -n $must_die_on_failure { die_with_status 2 $die_msg } else { setglobal err = ""$(err);$die_msg"" continue } } if test -n $recursive { shell { setglobal prefix = $[git submodule--helper relative-path "$prefix$sm_path/" $wt_prefix] setglobal wt_prefix = '' sanitize_submodule_env cd $sm_path && eval cmd_update } setglobal res = $Status if test $res -gt 0 { setglobal die_msg = $[eval_gettext "Failed to recurse into submodule path '\$displaypath'] if test $res -ne 2 { setglobal err = ""$(err);$die_msg"" continue } else { die_with_status $res $die_msg } } } } if test -n $err { setglobal OIFS = $IFS setglobal IFS = '';'' for e in [$err] { if test -n $e { echo >&2 $e } } setglobal IFS = $OIFS exit 1 } } } proc set_name_rev { setglobal revname = $[ shell { sanitize_submodule_env cd $1 && do { git describe $2 2>/dev/null || git describe --tags $2 2>/dev/null || git describe --contains $2 2>/dev/null || git describe --all --always $2 } }] test -z $revname || setglobal revname = "" ($revname)"" } # # Show commit summary for submodules in index or working tree # # If '--cached' is given, show summary between index and given commit, # or between working tree and given commit # # $@ = [commit (default 'HEAD'),] requested paths (default all) # proc cmd_summary { setglobal summary_limit = '-1' setglobal for_status = '' setglobal diff_cmd = 'diff-index' # parse $args after "submodule ... summary". while test $# -ne 0 { match $1 { with --cached setglobal cached = $1 with --files setglobal files = $1 with --for-status setglobal for_status = $1 with -n|--summary-limit setglobal summary_limit = $2 isnumber $summary_limit || usage shift with --summary-limit=* setglobal summary_limit = $(1#--summary-limit=) isnumber $summary_limit || usage with -- shift break with -* usage with * break } shift } test $summary_limit = 0 && return if setglobal rev = $[git rev-parse -q --verify --default HEAD $(1+"$1")] { setglobal head = $rev test $Argc = 0 || shift } elif test -z $1 || test $1 = "HEAD" { # before the first commit: compare with an empty tree setglobal head = $[git hash-object -w -t tree --stdin] test -z $1 || shift } else { setglobal head = '"HEAD'" } if test -n $files { test -n $cached && die $[gettext "The --cached option cannot be used with the --files option] setglobal diff_cmd = 'diff-files' setglobal head = '' } cd_to_toplevel eval "set $[git rev-parse --sq --prefix $wt_prefix -- @ARGV]" # Get modified modules cared by user setglobal modules = $[git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- @ARGV | sane_egrep '^:([0-7]* )?160000' | while read mod_src mod_dst sha1_src sha1_dst status sm_path { # Always show modules deleted or type-changed (blob<->module) if test $status = D || test $status = T { printf '%s\n' $sm_path continue } # Respect the ignore setting for --for-status. if test -n $for_status { setglobal name = $[git submodule--helper name $sm_path] setglobal ignore_config = $[get_submodule_config $name ignore none] test $status != A && test $ignore_config = all && continue } # Also show added or modified modules which are checked out env GIT_DIR="$sm_path/.git" git-rev-parse --git-dir >/dev/null 2>&1 && printf '%s\n' $sm_path }] test -z $modules && return git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- $modules | sane_egrep '^:([0-7]* )?160000' | cut -c2- | while read mod_src mod_dst sha1_src sha1_dst status name { if test -z $cached && test $sha1_dst = 0000000000000000000000000000000000000000 { match $mod_dst { with 160000 setglobal sha1_dst = $[env GIT_DIR="$name/.git" git rev-parse HEAD] with 100644 | 100755 | 120000 setglobal sha1_dst = $[git hash-object $name] with 000000 # removed with * # unexpected type eval_gettextln "unexpected mode \$mod_dst" >&2 continue } } setglobal missing_src = '' setglobal missing_dst = '' test $mod_src = 160000 && ! env GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_src^0 >/dev/null && setglobal missing_src = 't' test $mod_dst = 160000 && ! env GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_dst^0 >/dev/null && setglobal missing_dst = 't' setglobal display_name = $[git submodule--helper relative-path $name $wt_prefix] setglobal total_commits = '' match "$missing_src,$missing_dst" { with t, setglobal errmsg = $[eval_gettext " Warn: \$display_name doesn't contain commit \$sha1_src] with ,t setglobal errmsg = $[eval_gettext " Warn: \$display_name doesn't contain commit \$sha1_dst] with t,t setglobal errmsg = $[eval_gettext " Warn: \$display_name doesn't contain commits \$sha1_src and \$sha1_dst] with * setglobal errmsg = '' setglobal total_commits = $[ if test $mod_src = 160000 && test $mod_dst = 160000 { setglobal range = ""$sha1_src...$sha1_dst"" } elif test $mod_src = 160000 { setglobal range = $sha1_src } else { setglobal range = $sha1_dst } env GIT_DIR="$name/.git" \ git rev-list --first-parent $range -- | wc -l] setglobal total_commits = "" ($shExpr('$total_commits + 0'))"" } setglobal sha1_abbr_src = $[echo $sha1_src | cut -c1-7] setglobal sha1_abbr_dst = $[echo $sha1_dst | cut -c1-7] if test $status = T { setglobal blob = $[gettext "blob] setglobal submodule = $[gettext "submodule] if test $mod_dst = 160000 { echo "* $display_name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:" } else { echo "* $display_name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:" } } else { echo "* $display_name $sha1_abbr_src...$sha1_abbr_dst$total_commits:" } if test -n $errmsg { # Don't give error msg for modification whose dst is not submodule # i.e. deleted or changed to blob test $mod_dst = 160000 && echo $errmsg } else { if test $mod_src = 160000 && test $mod_dst = 160000 { setglobal limit = '' test $summary_limit -gt 0 && setglobal limit = ""-$summary_limit"" env GIT_DIR="$name/.git" \ git log $limit --pretty='format: %m %s' \ --first-parent $sha1_src...$sha1_dst } elif test $mod_dst = 160000 { env GIT_DIR="$name/.git" \ git log --pretty='format: > %s' -1 $sha1_dst } else { env GIT_DIR="$name/.git" \ git log --pretty='format: < %s' -1 $sha1_src } echo } echo } } # # List all submodules, prefixed with: # - submodule not initialized # + different revision checked out # # If --cached was specified the revision in the index will be printed # instead of the currently checked out revision. # # $@ = requested paths (default to all) # proc cmd_status { # parse $args after "submodule ... status". while test $# -ne 0 { match $1 { with -q|--quiet setglobal GIT_QUIET = '1' with --cached setglobal cached = '1' with --recursive setglobal recursive = '1' with -- shift break with -* usage with * break } shift } do { git submodule--helper list --prefix $wt_prefix @ARGV || echo "#unmatched" $Status } | while read mode sha1 stage sm_path { die_if_unmatched $mode $sha1 setglobal name = $[git submodule--helper name $sm_path] || exit setglobal url = $[git config submodule."$name".url] setglobal displaypath = $[git submodule--helper relative-path "$prefix$sm_path" $wt_prefix] if test $stage = U { say "U$sha1 $displaypath" continue } if test -z $url || do { ! test -d "$sm_path"/.git && ! test -f "$sm_path"/.git } { say "-$sha1 $displaypath" continue; } if git diff-files --ignore-submodules=dirty --quiet -- $sm_path { set_name_rev $sm_path $sha1 say " $sha1 $displaypath$revname" } else { if test -z $cached { setglobal sha1 = $[sanitize_submodule_env; cd $sm_path && git rev-parse --verify HEAD] } set_name_rev $sm_path $sha1 say "+$sha1 $displaypath$revname" } if test -n $recursive { shell { setglobal prefix = ""$displaypath/"" sanitize_submodule_env setglobal wt_prefix = '' cd $sm_path && eval cmd_status } || die $[eval_gettext "Failed to recurse into submodule path '\$sm_path'] } } } # # Sync remote urls for submodules # This makes the value for remote.$remote.url match the value # specified in .gitmodules. # proc cmd_sync { while test $# -ne 0 { match $1 { with -q|--quiet setglobal GIT_QUIET = '1' shift with --recursive setglobal recursive = '1' shift with -- shift break with -* usage with * break } } cd_to_toplevel do { git submodule--helper list --prefix $wt_prefix @ARGV || echo "#unmatched" $Status } | while read mode sha1 stage sm_path { die_if_unmatched $mode $sha1 setglobal name = $[git submodule--helper name $sm_path] setglobal url = $[git config -f .gitmodules --get submodule."$name".url] # Possibly a url relative to parent match $url { with ./*|../* # rewrite foo/bar as ../.. to find path from # submodule work tree to superproject work tree setglobal up_path = $[printf '%s\n' $sm_path | sed "s/[^/][^/]*/../g] && # guarantee a trailing / setglobal up_path = "$(up_path%/)/" && # path from submodule work tree to submodule origin repo setglobal sub_origin_url = $[git submodule--helper resolve-relative-url $url $up_path] && # path from superproject work tree to submodule origin repo setglobal super_config_url = $[git submodule--helper resolve-relative-url $url] || exit with * setglobal sub_origin_url = $url setglobal super_config_url = $url } if git config "submodule.$name.url" >/dev/null 2>/dev/null { setglobal displaypath = $[git submodule--helper relative-path "$prefix$sm_path" $wt_prefix] say $[eval_gettext "Synchronizing submodule url for '\$displaypath'] git config submodule."$name".url $super_config_url if test -e "$sm_path"/.git { shell { sanitize_submodule_env cd $sm_path setglobal remote = $[get_default_remote] git config remote."$remote".url $sub_origin_url if test -n $recursive { setglobal prefix = ""$prefix$sm_path/"" eval cmd_sync } } } } } } # This loop parses the command line arguments to find the # subcommand name to dispatch. Parsing of the subcommand specific # options are primarily done by the subcommand implementations. # Subcommand specific options such as --branch and --cached are # parsed here as well, for backward compatibility. while test $# != 0 && test -z "$command" { match $1 { with add | foreach | init | deinit | update | status | summary | sync setglobal command = $1 with -q|--quiet setglobal GIT_QUIET = '1' with -b|--branch match $2 { with '' usage } setglobal branch = $2; shift with --cached setglobal cached = $1 with -- break with -* usage with * break } shift } # No command word defaults to "status" if test -z $command { if test $Argc = 0 { setglobal command = 'status' } else { usage } } # "-b branch" is accepted only by "add" if test -n $branch && test $command != add { usage } # "--cached" is accepted only by "status" and "summary" if test -n $cached && test $command != status && test $command != summary { usage } "cmd_$command" @ARGV