STAY AHEAD OF GIT WITH THIS SHARP SCRIPT

I work on quite a few Git repositories at once, and I don’t always commit changes in one before making changes to another. Or if I do, I don’t always push the changes up straight away. That may not be best practice in software development, but hey, it’s what I do. The issue for me is remembering what state each repo is in. Here’s the script I use to tell me.

The script iterates through the contents of my git directory—stored in the shell variable $GIT—and, for directories containing a .git subdirectory, it uses the output of the git status command to check for uncommitted or unmerged changes.

The latter are revealed by the repo being ahead of the branch on origin. The script uses grep to look for the string is ahead in the output.

Uncommitted changes are revealed with the --porcelain flag, which has a non-empty output in this case, but otherwise returns nothing.

Since you can’t have both states at once, the script sets the repo state accordingly, and its value is then stored in an array which matches index for index an array of the names of repos with changes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if cd "$GIT"; then
    # Process the files
    for repo in *; do
        if [[ -d "$repo" && -d "$repo/.git" ]]; then
            if cd "$repo"; then
                local state=""
                local unmerged=$(git status)
                unmerged=$(grep 'is ahead' < <((echo -e "$unmerged")))
                if [[ -n "$unmerged" ]]; then
                    state="unmerged"
                fi

                local uncommitted=$(git status --porcelain --ignore-submodules)
                if [[ -n "$uncommitted" ]]; then
                    state="uncommitted"
                fi

                if [[ -n "$state" ]]; then
                    states+=("$state")
                    repos+=("$repo")
                    if [[ ${#repo} -gt $max ]] max=${#repo}
                fi

                cd ..
            fi
        fi
    done
fi

Why store all this in an array? That’s for the last part, which outputs the list of changed repos in tidy formatting laid out according to the repo with the longest name.

Every time the script finds a repo with changes, it checks the length of the repo name against the numeric value stored in the variable max:

1
if [[ ${#repo} -gt $max ]] max=${#repo}

If the repo’s name is longer than max, max is updated with that length.

In the output section, the script uses printf’s %*s formatter to right-align the text at the column indicated by the value of max:

1
printf '%*s has %s changes\n' $max ${repos[i]} ${states[i]}

The other two arguments are, respectively, the name of a repo and its state. A for loop is used to iterate through the changed repos list:

1
2
3
4
5
6
7
8
if [[ ${#repos} -eq 0 ]]; then
    echo "All repos up to date"
else
    echo "Repos with changes:"
    for (( i = 1 ; i <= ${#repos[@]} ; i++ )); do
        printf '%*s has %s changes\n' $max ${repos[i]} ${states[i]}
    done
fi

Before this, the script checks the length the repos array—${#repos}—to make sure there are changed repos to output.

You can get the full script from my Scripts repo. Which is up to date.

smittytonesmittytone
Site and software copyright © 2026, Tony Smith