Git Functions

git_functions.bsh

A few functions to make working with git and different versions of git easier

log_unpushed_commits

List all unpushed commits to stdout

List all commits on branches/tags that have not been pushed to a remote. This is a set operation: it lists all the commits from a branch in the DAG except those reachable by a remote-tracking branch. It does not require an associated remote-tracking branch

Arguments:
  • [$1] - A name of a remote to filter by; e.g., origin. If unset, then don’t filter

  • [$2...] - A branch/tag to filter by (or multiple branches/tags); e.g., main. If unset, then don’t filter

Output:
  • *stdout* - Print all unpushed commits for tracking branches to stdout

git_tracking_branch

Get the corresponding remote-tracking branch for a local-tracking branch

Arguments:
  • $1 - A local-tracking branch; e.g., main

Output:
  • *stdout* - The corresponding remote-tracking branch, or the empty string if there is no tracking branch

Note

  • A branch that you create locally and then push to a remote is not configured as a local-tracking branch by default. You must use the -u flag with git push or git branch.

  • A branch cannot track multiple remotes.

git_tracking_branches

Find all local- and corresponding remote-tracking branches

Arguments:
  • [$1...] - The name of a remote to filter by (or multiple remotes); e.g., origin. If unset, then don’t filter

Output:
  • vsi_git_tracking_branches - An array of local-tracking branch names

  • vsi_git_remote_tracking_branches - A corresponding array of remote-tracking branch names

Note

  • A branch that you create locally and then push to a remote is not configured as a local-tracking branch by default. You must use the -u flag with git push or git branch.

  • A branch cannot track multiple remotes.

log_outgoing_commits

List all unpushed commits for a tracking branch to stdout

Arguments:
  • $1... - A local-tracking branch (or multiple branches); e.g., main

Output:
  • *stdout* - Print all unpushed commits for the tracking branch to stdout

log_all_outgoing_commits

List all unpushed commits for tracking branches to stdout

Arguments:
  • [$1...] - The name of a remote to filter by (or multiple remotes); e.g., origin. If unset, then don’t filter

Output:
  • *stdout* - Print all unpushed commits for tracking branches to stdout

convert_git_remote_http_to_git

Change the remote url of a git repo to use the git@ syntax instead of https

In a git repository (or submodule), if the (last) URL of the remote is specified using the the https:// protocol, convert it to the ssh transfer protocol (git@..) to make pushing changes to a private remote server easier. Assume a very basic url format: https://git-server.com/co/project.git without port, query, or fragment sections. Warn if these exist. Do nothing if a pushurl is already configured

Arguments:
  • [$1] - The name of a remote to convert. Default: origin

Note

This does not change a submodule’s tracked URL (i.e., the URL in the .gitmodules file) nor a submodule’s URL (i.e., .git/config:submodule), only the URL of the remote in .git/**/config (e.g., .git/modules/docker/recipes/config:remote)

is_submodule
Output:
  • Returns 0 if the PWD is in a submodule, 1 for it is not

git_project_root_dir
Output:

*stdout* - The absolute path to the root of the topmost superproject’s working tree.

Example

If the vsi_common repository is cloned (recursively) at /src, and then git_project_root_dir will output /src/vsi_common. This is true regardless of where in the work-tree the function is run from, including from within submodules, sub-submodules, etc. Cf. git rev-parse –show-superproject-working-tree (available in git v2.13.7), which only outputs the absolute path to the working tree of the superproject of the current submodule.

git_project_git_dir
Output:

*stdout* - The absolute path to the git directory of the topmost superproject.

If the vsi_common repository is cloned (recursively) at /src, and then git_project_git_dir will output /src/vsi_common/.git. This is true regardless of where in the work-tree the function is run from, including from within submodules (e.g., docker/recipes), sub-submodules, etc. This function can also be run from within a bare repo (and from within a .git directory)

git_submodule_summary_recursive

Recursively log the commits differing between each submodule’s tracked commit and its current working tree

git_submodule_summary

Log the commits differing between each submodule’s tracked commit and its current working tree

Using git diff --submodule=log, compare the working tree of each submodule to the version committed to HEAD of the super project, and list any commits that are missing and/or now present in the submodule. For example, if an earlier commit in the submodule is checked out, there will be missing commits in the submodule according to the super project. Similarly, if a commit is made to the submodule, there will be new commits present in the submodule according to the super project. Of course, the commit graph could diverge in more complicated ways.

Output:
  • *stdout* - Each submodule’s differences in terms of commits

Note

This function iterates through each submodule in a depth-first traversal

git_submodule_displaypaths_recursive

Recursively get the displaypath to each submodule

git_submodule_displaypaths

Get the displaypath to each submodule

Get what git refers to as the displaypath of the submodule: the relative path from the current working directory to the root of the containing repository and then to each submodule. This path is defined by the git-specified environment variable, displaypath (or prefix in and before git v1.8.3).

Output:

stdout - The displaypath to each submodule (recursively); e.g., docker/recipes

Note

Submodule paths are not the same thing as submodule names.

Note

This function iterates through each submodule in the same order as git_submodule_urls, git_submodule_names and git_submodule_toplevels (i.e., a depth-first traversal)

Note

Because this function uses git submodule foreach, it skips submodules that have been init’d but not updated.

git_submodule_urls_recursive

Recursively get all submodule URLs

git_submodule_urls

Get all submodule URLs

Get the (last) URL for each submodule (recursively) from its .git/**/config file (e.g., .git/modules/docker/recipes/config) for the specified remote (origin by default). Note: Since git v1.8, a remote can have multiple associated URLs (and pushurls) (see .git/config).

Arguments:

[$1] - The name of a remote. Note: this same remote must exist in all submodules. Default: origin

Output:

stdout - The URL of each submodule (recursively); e.g., https://github.com/VisionSystemsInc/docker_recipes.git

Note

The ultimate source-of-truth of a submodule’s URL is in its .gitmodules file. If the URL is updated in this file, then the repository’s and submodule’s configurations will be out-of-date. git submodule sync will update these configurations using the .gitmodules file and the default remote of the current branch, if set; otherwise origin. git_submodule_sync will do the same but using the specified remote.

Note

This function iterates through each submodule in the same order as git_submodule_displaypaths, git_submodule_names and git_submodule_toplevels (i.e., a depth-first traversal)

Note

Because this function uses git submodule foreach, it skips submodules that have been init’d but not updated. Cf. git_mirror get_config_submodule_names, which will (non-recursively) list submodules that have only been init’d. The associated URL can then be queried for a given submodule name with git config --get submodule.<name>.url as git_mirror clone_submodules does.

git_submodule_names_recursive

Recursively get the name of each submodule

git_submodule_names

Get the name of each submodule

Get the name of the submodule as defined in the .gitmodules file. This name is, by default, the same as the path to the submodule; however, this is not necessarily the case.

Output:

stdout - The name of each submodule (recursively); e.g., docker/recipes

Note

Submodule names are not the same thing as submodule paths.

Note

This function iterates through each submodule in the same order as git_submodule_displaypaths, git_submodule_urls and git_submodule_toplevels (i.e., a depth-first traversal)

Note

Because this function uses git submodule foreach, it skips submodules that have been init’d but not updated. Cf. git_mirror get_config_submodule_names (it queries the current repository’s submodule names from its configuration), which will list submodules that have been init’d but not updated, and just_git_functions.bsh submodule-helper-list, which will list all submodules, even those not init’d (it queries the current repository’s submodule names and paths from the .gitmodules file). Although these functions are not recursive, they both are used to recurse manually through all the submodules (albeit, in a different order than this function).

git_submodule_toplevels_recursive

Recursively get the path to the top-level directory of each submodule

git_submodule_toplevels

Get the path to the top-level directory of each submodule

Get the path to what git refers to as the top-level directory of the submodule; i.e., the absolute path to the top-level of the immediate superproject. For example, the vsi_common repository has a submodule at docker/recipes. If the repository is cloned in /src, then the top-level directory for the submodule docker/recipes is /src/vsi_common.

This function, along with git_submodule_names, could be used to, for example, cd to the top-level of each immediate superproject and then query the repository’s submodule configuration using git config submodule.<name>.<key>; e.g., git config submodule.docker/recipes.url.

Output:

stdout - The top-level directory of each submodule (recursively); e.g., /src/vsi_common

Note

This function iterates through each submodule in the same order as git_submodule_displaypaths, git_submodule_urls and git_submodule_names (i.e., a depth-first traversal)

Note

Because this function uses git submodule foreach, it skips submodules that have been init’d but not updated.

submodule-helper-relative-url

Essentially git submodule--helper relative-url (if it existed)

Resolve a submodule’s relative URL based on the parent URL. This function works by creating a dummy git repository and submodule and then running git submodule init (which runs a process similar to git submodule sync), after which, the resolved URL can be queried with git config submodule.submod.url. We do this to avoid modifying the existing repository’s git configuration (e.g., git config submodule.docker/recipes.url).

Arguments:
  • $1 - URL of the parent repository; e.g., https://github.com/VisionSystemsInc/vsi_common.git per git config remote.origin.url

  • $2 - A relative submodule URL—typically from the .gitmodules file; e.g., ../../VisionSystemsInc/docker_recipes.git per git config -f .gitmodules submodule.docker/recipes.url

  • [$3] - Up path: path from the submodule to the parent repository; e.g., ../../. Must have a trailing slash. (Not strictly necessary if $1 is an absolute path)

Output:

stdout - Resolved URL; e.g., https://github.com/VisionSystemsInc/docker_recipes.git

Note

This is essentially a bash port of the function relative_url() (called from resolve_relative_url(), aka git submodule--helper resolve-relative-url, which itself is called from cmd_sync(), aka git submodule sync. git submodule--helper resolve-relative-url, which is available in git v2.9 and after, does not allow us to specify (directly) the remote URL from which to resolve a relative URL: it resolves relative URLs based on the remote URL, git config remote.${remote_name}.url, where remote_name is the default remote of the current branch, if set; otherwise origin.

git_submodule_sync_recursive

git submodule sync --recursive but using a specific remote (just_upstream by default)

git_submodule_sync

git submodule sync but using a specific remote (just_upstream by default)

Like git submodule sync, synchronize the configured URL of each submodule in a repository (recursively) to the value specified in .gitmodules; however, instead of overwriting the default remote (origin), update the remote specified by JUST_GIT_UPSTREAM (just_upstream by default).

Arguments:

[$1] - The project-repository’s remote. Default: origin [$2...] - The submodule path to sync (or multiple submodules). Default: all submodules of the current repository (non-recursively)

Parameters:
  • [JUST_GIT_UPSTREAM] - The submodules’ remote to sync. When updating the URL in the submodules’ configuration, use this remote. Default: just_upstream

Note

Because this function uses git submodule foreach, it skips submodules that have been init’d but not updated.

Note

Creates the just_upstream remote just like, and using the same procedure as, just_git_functions.bsh safe_git_submodule_update; however, importantly, it does not try to update the submodule to the expected SHA.

git_submodule_is_published_recursive

Ensure all submodules have pushed the necessary changes to their public repo

Check each submodule (recursively) to see if the SHA tracked by its parent repository has been pushed to the URL tracked by the parent repository (per its .gitmodules file). Must be run from the root of a repository.

Arguments:

[$1] - The project-repository’s remote. Default: origin

Parameters:
  • [JUST_GIT_UPSTREAM] - The submodule’s remote to sync. When fetching, use this remote’s URL. Default: just_upstream

Output:

Return 0 if, for each submodule (recursively), the changes tracked by the parent repository have been made public; otherwise, return 1

Note

Creates a JUST_GIT_UPSTREAM remote. See git_submodule_sync.

Note

Cf. git push --recurse-submodules=check (available since git v1.8.0) will check for unpushed commits in a submodule for its current committed state in the parent project. However, this only seems to work if it is used with a set of outgoing commits that contains a change to a submodule. And, despite its name, it does not seem to recurse into submodules.