John Wooten
John Wooten

Reputation: 745

What bash command can be used to tell if a local git repository is out of sync with its remote

Searching in StackOverflow and on Google, I can find lots of items about how to sync, and do everything with git, except I can't find a reliable way using a bash script to tell if my local repository is in sync with its remote. I've tried:

1.

git branch -v |
perl -wlne'
   print "$ENV{reponame} [$1] --> $3 $2"
      if /^..(\S+)\s+([a-f0-9]+)\s+(\[(?:ahead|behind)\s+\d+\])/
' |
while IFS= read -r MOD; do
   printf ' %s\n' "$MOD"  # Replace with code that uses $MOD
done

and,

2.

isInSync=`git remote update>/dev/null; git status -uno | grep -o "is up to date"`
if [ $? -ne 0  ]; then
    echo " $reponame [$br] --> Out of sync with remote at $d"
    ok=false
fi

and have been trying to find a way to use:

  1. git log --oneline | head -n1 | sed # this I don't know.

to try to get the first word in the log file, which is the commit hash of the last commit, and compare it with:

rev=$(git rev-parse --short HEAD)

which is the commit hash of the local repository branch you are in.

The problem with #1 is that it doesn't seem to pick up when the local is out of sync with the remote.

The problem with # 2, is that it causes the local .git/config to get involved and produces odd attempts to access different remote repositories like heroic.

The problem with #3 is that I can't figure out how to get the hash code from the git log and then compare it to the $rev above. It would seem to be the best bet as when I check it on different computers in different states, it seems to convey the right information.

I am writing a bash script that checks a group of git projects and tells me their states, i.e. up to date, untracked files, uncommitted files, and out of sync with the remote.

Help would be appreciated in either suggesting a better way or how to do the extraction of the commit-hash from the log and compare it to the current commit-hash of the local last commit.

Upvotes: 0

Views: 838

Answers (3)

John Nagle
John Nagle

Reputation: 875

OK. I'm up to date locally, but out of sync with remote. So "git status" says

On branch main
Your branch is up to date with 'origin/main'

even though changes to "main" have been made from another computer.

In this situation

git remote show origin

provides useful info. The documentation for that command just says "Gives some information about the remote ." What I get looks like this:

$ git remote show origin
* remote origin
  Fetch URL: https://github.com/user/project.git
  Push  URL: https://github.com/user/project.git
  HEAD branch: main
  Remote branches:
    damaged-mesh   new (next fetch will store in remotes/origin)
    main           tracked
    rend3-update-1 new (next fetch will store in remotes/origin)
  Local branch configured for 'git pull':
    main merges with remote main
  Local ref configured for 'git push':
    main pushes to main (local out of date)

That's correct, and it tells me that local is out of date.

Another project, which is in sync, returns

  Local refs configured for 'git push':
    main  pushes to main  (up to date)
    stats pushes to stats (up to date)

What I want to do, as part of a script that packages releases, is to make sure that local is up to date with remote, and abort the release packager if it isn't. But I don't want to change any local state with the check, so I don't want to fetch.

Is checking git status for

Your branch is up to date with 'origin/main'

and git remote show origin for

main  pushes to main  (up to date)

sufficient to make sure that this is a build from a clean "main"? Or is there anything else I should check?

Upvotes: 0

John Wooten
John Wooten

Reputation: 745

In the above code for git_check, I replace the section: 2.

isInSync=`git remote update>/dev/null; git status -uno | grep -o "is up to date"`
if [ $? -ne 0  ]; then
    echo " $reponame [$br] --> Out of sync with remote at $d"
    ok=false
fi

with:

last_commit=`git log --oneline | head -n1 | grep -o "^\w*\b"`
rev=$(git rev-parse --short HEAD)
if [ "$last_commit" != "$rev"  ]; then
    echo " $reponame [$br] --> Out of sync with remote at $d"
    ok=false
fi

Here is the Console output with some comments

Modify a file that has been committed.

[:~/bin] develop(+4/-2) 2s ± git status
On branch develop
Your branch is up to date with 'origin/develop'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   git_check

no changes added to commit (use "git add" and/or "git commit -a")

[:~/bin] develop(+4/-2) ± git_check
 bin [develop] --> Changes to be staged/committed at /Users/userid/bin

[:~/bin] develop(+4/-2) 128 ± git add -A
[:~/bin] develop(+0/-0) ± git diff --cached --shortstat
 1 file changed, 4 insertions(+), 2 deletions(-)

[:~/bin] develop(+0/-0) ± git_check
 bin [develop] --> Changes to be committed at /Users/userid/bin

[:~/bin] develop(+0/-0) 2s ± git commit -m "Better way to check for remote sync"[develop fab4f1d] Better way to check for remote sync
 1 file changed, 4 insertions(+), 2 deletions(-)
[:~/bin] develop(1) ± git_check
 OK --> bin [develop] fab4f1d
 bin [develop] --> [ahead 1] fab4f1d

[:~/bin] develop(1) 2s ± git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 416 bytes | 416.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/userid/repo.git
   ee0f0ca..fab4f1d  develop -> develop
[:~/bin] develop 4s ± git_check
 OK --> bin [develop] fab4f1d

For me, at least, this appears to solve my problem. As the console output shows, it gives me information about what is happening at each stage of the workflow. Especially, it tells me that my local repository is ahead of the remote repository by 1, telling me I need to do a push. On the other machines, I will see that they are behind by 1, telling me I need to do a pull to sync up.

Thanks to torek for the helpful information.

Upvotes: 0

torek
torek

Reputation: 488123

As you are seeing, you have to define what you mean by in sync.

A Git repository first and foremost is a way to hold commits. The set of commits in the repository, as found by branch and tag names and other such names, is what is in the repository. The commits are the history.

Most Git repositories that users work with, however, are not "bare". They have a work-tree or working tree into which files can be extracted from any commit, and/or additional files created and/or various files modified and so on. This working area can be "clean" (matches a commit, more or less) or "dirty".

Is a "dirty" repository, with lots of work going on inside it, "in sync" with some other repository, even if both repositories have exactly the same set of commits? Or are these "not in sync"? What if the other repository has a work-tree and it's "dirty" in exactly the same way? That's something you need to define.

(Besides the work-tree, all repositories—even bare ones—have an index as well, which can also be "clean" or "dirty", and perhaps you should factor that in as well.)

A repository can have one or more remotes defined as well. Given a remote name, such as origin, Git can be told: connect to some network URL and obtain new commits from a Git over there, and put them into this repository. That's what your git remote update is doing. How many remotes that contacts—it could get some of them, or all of them, or maybe some are unreachable at the moment—is difficult to answer, as this is all quite configurable.

... [get] the commit hash of the last commit

Each branch name automatically holds the hash ID of the last commit in that branch. There can be more than one "last commit", in other words. Using HEAD is the right way to find the hash ID of the current commit, but this may not be the tip commit of any branch:

rev=$(git rev-parse HEAD)

If HEAD contains the name of an unborn branch, this step will fail. That state is the case in any totally-empty repository (because there are no commits, hence there can be no branch names; branch names are required to name some existing commit). It's also the state after a git checkout --orphan operation, however.

I am writing a bash script that checks a group of git projects and tells me their states, i.e. up to date, untracked files, uncommitted files, and out of sync with the remote.

So, you get to choose how to define each of these things.

In general, I would:

  • Optionally, have each repository contact its main upstream(s), whatever those may be: probably those defined by whatever git remote update does; consider here whether it's good or bad to allow --prune (see also the fetch.prune setting).
  • Check at least the current branch, as reported by git symbolic-ref HEAD: if this command fails, there is no current branch, i.e., we're on a detached HEAD.
  • Check the status as reported by git status --porcelain=v2 or similar, to look at the state of the index and work-tree. Consider checking submodule status here as well; git status may do this for you, or not, depending on settings.
  • Use the current branch's upstream setting, if (a) there is a current branch and (b) it has an upstream. This upstream setting is often the name of a branch in another Git repository. (It can instead be the name of a branch in this repository.) If so, use git rev-list --left-right --count branch...branch@{u} to count the number of commits ahead and/or behind, perhaps after the git remote update with or without --prune.
  • Optionally, check each branch name, keeping in mind that each Git repository has its own branch names and there's no particular reason, other than convenience and convention, to use the same names in two different Git repositories. That is, my dev might not be related to origin/dev, for instance. If my dev goes with origin/develop I probably set the upstream of dev to origin/develop, so consider checking each branch's upstream. Note that git branch -vv does this (and also counts ahead/behind values, unless told not to, and also has extra support for added work-trees now).

Except for current branch and dirtiness (which git status already reports), most of the work is just git remote update -p and git branch -vv, really.

Upvotes: 1

Related Questions