Skip to content

Commit

Permalink
push: point to 'git pull' and 'git push --force' in case of non-fast …
Browse files Browse the repository at this point in the history
…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 <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
Signed-off-by: Nanako Shiraishi <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
  • Loading branch information
moy authored and gitster committed Aug 12, 2009
1 parent e89df7d commit 07436e4
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 5 deletions.
86 changes: 86 additions & 0 deletions Documentation/git-push.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------

Expand Down
9 changes: 8 additions & 1 deletion builtin-push.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand Down
10 changes: 7 additions & 3 deletions transport.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -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);

Expand All @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion transport.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down

0 comments on commit 07436e4

Please sign in to comment.