push: point to 'git pull' and 'git push --force' in case of non-fast forward
'git push' failing because of non-fast forward is a very common situation, and a beginner does not necessarily understand "fast forward" immediately. Add a new section to the git-push documentation and refer them to it. Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr> Signed-off-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Nanako Shiraishi <nanako3@lavabit.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
e89df7dcda
commit
07436e43da
@ -195,6 +195,92 @@ reason::
|
||||
refs, no explanation is needed. For a failed ref, the reason for
|
||||
failure is described.
|
||||
|
||||
Note about fast-forwards
|
||||
------------------------
|
||||
|
||||
When an update changes a branch (or more in general, a ref) that used to
|
||||
point at commit A to point at another commit B, it is called a
|
||||
fast-forward update if and only if B is a descendant of A.
|
||||
|
||||
In a fast-forward update from A to B, the set of commits that the original
|
||||
commit A built on top of is a subset of the commits the new commit B
|
||||
builds on top of. Hence, it does not lose any history.
|
||||
|
||||
In contrast, a non-fast-forward update will lose history. For example,
|
||||
suppose you and somebody else started at the same commit X, and you built
|
||||
a history leading to commit B while the other person built a history
|
||||
leading to commit A. The history looks like this:
|
||||
|
||||
----------------
|
||||
|
||||
B
|
||||
/
|
||||
---X---A
|
||||
|
||||
----------------
|
||||
|
||||
Further suppose that the other person already pushed changes leading to A
|
||||
back to the original repository you two obtained the original commit X.
|
||||
|
||||
The push done by the other person updated the branch that used to point at
|
||||
commit X to point at commit A. It is a fast-forward.
|
||||
|
||||
But if you try to push, you will attempt to update the branch (that
|
||||
now points at A) with commit B. This does _not_ fast-forward. If you did
|
||||
so, the changes introduced by commit A will be lost, because everybody
|
||||
will now start building on top of B.
|
||||
|
||||
The command by default does not allow an update that is not a fast-forward
|
||||
to prevent such loss of history.
|
||||
|
||||
If you do not want to lose your work (history from X to B) nor the work by
|
||||
the other person (history from X to A), you would need to first fetch the
|
||||
history from the repository, create a history that contains changes done
|
||||
by both parties, and push the result back.
|
||||
|
||||
You can perform "git pull", resolve potential conflicts, and "git push"
|
||||
the result. A "git pull" will create a merge commit C between commits A
|
||||
and B.
|
||||
|
||||
----------------
|
||||
|
||||
B---C
|
||||
/ /
|
||||
---X---A
|
||||
|
||||
----------------
|
||||
|
||||
Updating A with the resulting merge commit will fast-forward and your
|
||||
push will be accepted.
|
||||
|
||||
Alternatively, you can rebase your change between X and B on top of A,
|
||||
with "git pull --rebase", and push the result back. The rebase will
|
||||
create a new commit D that builds the change between X and B on top of
|
||||
A.
|
||||
|
||||
----------------
|
||||
|
||||
B D
|
||||
/ /
|
||||
---X---A
|
||||
|
||||
----------------
|
||||
|
||||
Again, updating A with this commit will fast-forward and your push will be
|
||||
accepted.
|
||||
|
||||
There is another common situation where you may encounter non-fast-forward
|
||||
rejection when you try to push, and it is possible even when you are
|
||||
pushing into a repository nobody else pushes into. After you push commit
|
||||
A yourself (in the first picture in this section), replace it with "git
|
||||
commit --amend" to produce commit B, and you try to push it out, because
|
||||
forgot that you have pushed A out already. In such a case, and only if
|
||||
you are certain that nobody in the meantime fetched your earlier commit A
|
||||
(and started building on top of it), you can run "git push --force" to
|
||||
overwrite it. In other words, "git push --force" is a method reserved for
|
||||
a case where you do mean to lose history.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
|
@ -140,6 +140,7 @@ static int do_push(const char *repo, int flags)
|
||||
struct transport *transport =
|
||||
transport_get(remote, url[i]);
|
||||
int err;
|
||||
int nonfastforward;
|
||||
if (receivepack)
|
||||
transport_set_option(transport,
|
||||
TRANS_OPT_RECEIVEPACK, receivepack);
|
||||
@ -148,13 +149,19 @@ static int do_push(const char *repo, int flags)
|
||||
|
||||
if (flags & TRANSPORT_PUSH_VERBOSE)
|
||||
fprintf(stderr, "Pushing to %s\n", url[i]);
|
||||
err = transport_push(transport, refspec_nr, refspec, flags);
|
||||
err = transport_push(transport, refspec_nr, refspec, flags,
|
||||
&nonfastforward);
|
||||
err |= transport_disconnect(transport);
|
||||
|
||||
if (!err)
|
||||
continue;
|
||||
|
||||
error("failed to push some refs to '%s'", url[i]);
|
||||
if (nonfastforward) {
|
||||
printf("To prevent you from losing history, non-fast-forward updates were rejected.\n"
|
||||
"Merge the remote changes before pushing again.\n"
|
||||
"See 'non-fast forward' section of 'git push --help' for details.\n");
|
||||
}
|
||||
errs++;
|
||||
}
|
||||
return !!errs;
|
||||
|
10
transport.c
10
transport.c
@ -820,7 +820,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
|
||||
}
|
||||
|
||||
static void print_push_status(const char *dest, struct ref *refs,
|
||||
int verbose, int porcelain)
|
||||
int verbose, int porcelain, int * nonfastforward)
|
||||
{
|
||||
struct ref *ref;
|
||||
int n = 0;
|
||||
@ -835,11 +835,14 @@ static void print_push_status(const char *dest, struct ref *refs,
|
||||
if (ref->status == REF_STATUS_OK)
|
||||
n += print_one_push_status(ref, dest, n, porcelain);
|
||||
|
||||
*nonfastforward = 0;
|
||||
for (ref = refs; ref; ref = ref->next) {
|
||||
if (ref->status != REF_STATUS_NONE &&
|
||||
ref->status != REF_STATUS_UPTODATE &&
|
||||
ref->status != REF_STATUS_OK)
|
||||
n += print_one_push_status(ref, dest, n, porcelain);
|
||||
if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD)
|
||||
*nonfastforward = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -997,7 +1000,8 @@ int transport_set_option(struct transport *transport,
|
||||
}
|
||||
|
||||
int transport_push(struct transport *transport,
|
||||
int refspec_nr, const char **refspec, int flags)
|
||||
int refspec_nr, const char **refspec, int flags,
|
||||
int * nonfastforward)
|
||||
{
|
||||
verify_remote_names(refspec_nr, refspec);
|
||||
|
||||
@ -1024,7 +1028,7 @@ int transport_push(struct transport *transport,
|
||||
|
||||
ret = transport->push_refs(transport, remote_refs, flags);
|
||||
|
||||
print_push_status(transport->url, remote_refs, verbose | porcelain, porcelain);
|
||||
print_push_status(transport->url, remote_refs, verbose | porcelain, porcelain, nonfastforward);
|
||||
|
||||
if (!(flags & TRANSPORT_PUSH_DRY_RUN)) {
|
||||
struct ref *ref;
|
||||
|
@ -68,7 +68,8 @@ int transport_set_option(struct transport *transport, const char *name,
|
||||
const char *value);
|
||||
|
||||
int transport_push(struct transport *connection,
|
||||
int refspec_nr, const char **refspec, int flags);
|
||||
int refspec_nr, const char **refspec, int flags,
|
||||
int * nonfastforward);
|
||||
|
||||
const struct ref *transport_get_remote_refs(struct transport *transport);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user