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
|
refs, no explanation is needed. For a failed ref, the reason for
|
||||||
failure is described.
|
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
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -140,6 +140,7 @@ static int do_push(const char *repo, int flags)
|
|||||||
struct transport *transport =
|
struct transport *transport =
|
||||||
transport_get(remote, url[i]);
|
transport_get(remote, url[i]);
|
||||||
int err;
|
int err;
|
||||||
|
int nonfastforward;
|
||||||
if (receivepack)
|
if (receivepack)
|
||||||
transport_set_option(transport,
|
transport_set_option(transport,
|
||||||
TRANS_OPT_RECEIVEPACK, receivepack);
|
TRANS_OPT_RECEIVEPACK, receivepack);
|
||||||
@ -148,13 +149,19 @@ static int do_push(const char *repo, int flags)
|
|||||||
|
|
||||||
if (flags & TRANSPORT_PUSH_VERBOSE)
|
if (flags & TRANSPORT_PUSH_VERBOSE)
|
||||||
fprintf(stderr, "Pushing to %s\n", url[i]);
|
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);
|
err |= transport_disconnect(transport);
|
||||||
|
|
||||||
if (!err)
|
if (!err)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
error("failed to push some refs to '%s'", url[i]);
|
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++;
|
errs++;
|
||||||
}
|
}
|
||||||
return !!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,
|
static void print_push_status(const char *dest, struct ref *refs,
|
||||||
int verbose, int porcelain)
|
int verbose, int porcelain, int * nonfastforward)
|
||||||
{
|
{
|
||||||
struct ref *ref;
|
struct ref *ref;
|
||||||
int n = 0;
|
int n = 0;
|
||||||
@ -835,11 +835,14 @@ static void print_push_status(const char *dest, struct ref *refs,
|
|||||||
if (ref->status == REF_STATUS_OK)
|
if (ref->status == REF_STATUS_OK)
|
||||||
n += print_one_push_status(ref, dest, n, porcelain);
|
n += print_one_push_status(ref, dest, n, porcelain);
|
||||||
|
|
||||||
|
*nonfastforward = 0;
|
||||||
for (ref = refs; ref; ref = ref->next) {
|
for (ref = refs; ref; ref = ref->next) {
|
||||||
if (ref->status != REF_STATUS_NONE &&
|
if (ref->status != REF_STATUS_NONE &&
|
||||||
ref->status != REF_STATUS_UPTODATE &&
|
ref->status != REF_STATUS_UPTODATE &&
|
||||||
ref->status != REF_STATUS_OK)
|
ref->status != REF_STATUS_OK)
|
||||||
n += print_one_push_status(ref, dest, n, porcelain);
|
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 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);
|
verify_remote_names(refspec_nr, refspec);
|
||||||
|
|
||||||
@ -1024,7 +1028,7 @@ int transport_push(struct transport *transport,
|
|||||||
|
|
||||||
ret = transport->push_refs(transport, remote_refs, flags);
|
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)) {
|
if (!(flags & TRANSPORT_PUSH_DRY_RUN)) {
|
||||||
struct ref *ref;
|
struct ref *ref;
|
||||||
|
@ -68,7 +68,8 @@ int transport_set_option(struct transport *transport, const char *name,
|
|||||||
const char *value);
|
const char *value);
|
||||||
|
|
||||||
int transport_push(struct transport *connection,
|
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);
|
const struct ref *transport_get_remote_refs(struct transport *transport);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user