I want to ensure that no commits are deleted (or squashed) from my local repository by fetch
from a remote repository.
Also I need to reveal to me, if any commits were deleted (or squashed) at the remote repository.
How can I make sure that my fetch
or pull
does not delete any commits (on my local PC)?
Another related question: How to make Git to delete local commits corresponding to deleted remote commits?
There are some problems with this question, and maybe that's why it got down-voted, but we can make something out of it.
Let's take a look at what's actually stored in a Git repository, drawn as a graph, before and after git fetch
, to see what I mean.
We'll start with this:
A <-- master
/
...--o--* <-- origin/master
\
B--C <-- origin/dev
Here, in the local repository, before running git fetch origin
, we have:
*
and working backwards;A
, on our master
, that is not on origin/master
dev
which is our origin/dev
: commits B
and C
.Now we run git fetch
, which prints some output, which for some reason we don't see or have forgotten. We then inspect our graph carefully again and discover this:
A <-- master
/
...--o--*--D <-- origin/master
\
E <-- origin/dev
That is, the two commits I labeled B
and C
are no longer reachable at all, from any of the origin/*
names, while new commits D
and E
have appeared. The name origin/master
, which is our Git's memory of their master
, now identifies commit D
, while the name origin/dev
identifies commit E
.
It's reasonably likely—but impossible to tell for sure from this amnesiac view—that commit D
is the result of squashing commits B
and C
, and possibly some additional commits that we never had. Commit E
is probably entirely new. On the other hand, we just don't know.
We probably have more information than this, though. In particular, if we have reflogs enabled, we will have origin/master@{1}
and origin/dev@{1}
. These will point to commits *
and C
respectively, and we'll retain both C
and B
through the origin/master@{1}
reflog.
We might also have a different starting picture. For instance, maybe our starting image looks like this instead:
A <-- master
/
...--o--* <-- origin/master
\
B--C <-- dev, origin/dev
In this case our ending picture looks like this:
A <-- master
/
...--o--*--D <-- origin/master
\ \
\ E <-- origin/dev
\
B--C <-- dev
There are several things to note here:
Hence:
I want to ensure that no commits are deleted (or squashed) from my local repository by fetch from a remote repository.
This is just automatically true, if by "no commits" you mean "no commits to which I have names other than remote-tracking names".
Also I need to reveal to me, if any commits were deleted (or squashed) at the remote repository.
Here, you can look at the difference between any updated remote-tracking names—note that git fetch
tells you which names it is updating—and their saved reflog values. If, for instance, origin/master
and origin/dev
were updated, you can compare origin/master@{1}
with origin/master
.
At this point, several of the special syntaxes described in the gitrevisions documentation become quite handy. In particular the two-dot and three-dot set-difference and symmetric-difference operators tell us just what we want to know:
git rev-list origin/master@{1}..origin/master
produces the list of commits added to origin/master
, and:
git rev-list origin/master..origin/master@{1}
produces the list of commits removed from origin/master. We still have all of those commits in our repository since the reflog names point to them; they won't vanish until the reflog entries expire.
We can use the three-dot syntax along with --left-right
, or similar entities, to enumerate and identify commits added and removed, marking which "side" of the symmetric difference they are on:
git rev-list --left-right origin/master@{1}...origin/master
Using all of this information we can automatically guess, if we really care, which commits might be rebases or squashes of which other commits, but this is a doomed effort in some cases (e.g., resolved merge conflicts).
Another related question: How to make Git to delete local commits corresponding to deleted remote commits?
This is trickier as it depends on what you mean. However, if you mean what I am guessing you mean, the --fork-point
operation of git rebase
is actually designed to handle this. The fork-point code uses the reflog of the upstream branch. For details, see Git rebase - commit select in fork-point mode.