On Thu, 17 Sep 2015 07:22:27 -0700 (PDT)
Machiel Kolstein <mkolst...@ifae.es> wrote:

> I am a newbie with GIT. I previously only had experience with CVS and
> SVN.

As happens quite commonly, this experience is actively hurting you ;-)
But we'll try to help this.

> I am working with some other people on a common project, with some
> main common source, called "origin", or what in GIT is called
> "master" (I think).

No.  Centralized systems, CVS and Subversion included have a split
between the client and the server; the client at any given time
maintains only a single revision from the server, only the server
contains the complete history, and all commits actually happen on the
server.  Contrary to this, Git is a distributed system.  This means two
things.  The first is that there are really no servers and clients;
every clone has all the history, and what your team designates to be
"the server" is only by your policy; otherwise the repository on the
server is nothing special.  The second thing is that Git is able to
communicate with any number of other repositories by receiving bits of
history from them and thending its own to them.  That thing called
"origin" is called "remote" in Git's parlance and is an entry in the
configuration of your local repository which "knows" how to contact a
particular remote repository -- in your case that one you've cloned
your local repo from.  The name "origin" is nothing special -- again,
just a policy naturally suggesting that this particular remote
designates the origin of your local repository, that is, where you
originally obtained this stuff from.  There may be any number of such
named remotes configured in your local repository.

The name "master" is the name of a branch.  Branches in Subversion are
vastly different in their semantics compared to Git but you already
should maintain the knowledge that branch is a line of development (a
chain of commits) contained *in* a repository.  In Git, these concepts
are the same: a branch designates a line of development and is
contained in a repository.

When you cloned the source repository, your local repository received
copies of all the branches -- including the branch "master" in *that*
repository.

...

> I got my local working copy by using "git clone".
> Now, in my local working copy I sometimes make small changes only for 
> myself (for readability, like newlines, or more spaces or tabs, or
> small comments). However, I also want to stay up-to-date with the
> main "master code", so I want to do a "git -pull".

Well, this impression is not correct: you may do `git pull` for this,
but certainly not oblidged to do that, and actually I'd say in your
case this is supposedly a wrong thing to do.  We'll get to this in a
moment.

> What I expected what would happen is that git would warn me that
> there are some conflicts (in my local working code) and I would have
> to edit these conflicts out from my local code.
> Which is fine with me.
> But, instead of that, GIT gives me the following error:
> [CODE]
> error: Your local changes to the following files would be overwritten
> by merge:
>         include/<somecode>.hh
>         src/<somecode>.cc
> Please, commit your changes or stash them before you can merge.
> [/CODE]

This is a quite sensible expectation but Git does not work this way.
`git pull` -- simplified -- performs two operations in this order:
`git fetch` to get the relevant data from the specified source and then
`git merge` to merge the result of the fetch with the tip of the branch
you currently have checking out.  To recap, `git merge` works with two
tips of branches.  It is able to deal with certain kinds of uncommitted
changes but not with those which overlap with the files it detects
would be updated by the merge.  To cite the `git merge` manual page:

| PRE-MERGE CHECKS
|  Before applying outside changes, you should get your own work in
|  good shape and committed locally, so it will not be clobbered if
|  there are conflicts. See also git-stash(1). git pull and git merge
|  will stop without doing anything when local uncommitted changes
|  overlap with files that git pull/git merge may need to update.
|
|  To avoid recording unrelated changes in the merge commit, git
|  pull and git merge will also abort if there are any changes
|  registered in the index relative to the HEAD commit. (One exception
|  is when the changed index entries are in the state that would result
|  from the merge already.)

So I reckon you might have mentally mapped `git pull` onto `svn update`
but that's wrong.  The reason is that in a centralized system you
simply can't have any commits on a branch the server does not yet have,
and hence all the changes you might ever have compared to the server in
centralized system is local changes.  Once you commit, they cease to be
changes.  Contrary to this, when you record a commit in Git, that's
actually all what happens: the commit is stored locally on the current
branch but is not sent anywhere.  All history "sharing" require an
explicit action on your side which is called "pushing" and is done using
the `git push` command.

As you can see, `git merge` which is there to "glue" your local changes
with someone else's changes naturally expects to operate on two chains
of commits -- the sides of the merge.  In this model, local changes are
sort of "in another league" ;-)

This might initially scary you off as a useless complication but please
keep in mind that a distributed system simply has no other way to
operate.

> Now, I don't want to commit my changes, because they would only cause 
> unnecessary conflicts for my other colleagues and most of these
> changes are not relevant for anybody else.

A useful concept to keep in your mind when working with a DVCS is
that until you pushed, all those unpushed commits are "truly yours" and
you're free to do whatever you want with them: rearrange, split, squash,
chop off the head of their branch while keeping their changes in your
work tree and so on.  This turns committing into a way more easy-peasy
light-hearted operation than it's known to be in centralized VCSes: if
you don't like a commit you've just created, undoing or re-doing it is
a no-brainer.  You're also free to never ever push your commits
anywhere, and they forever will be confined in your local repository.

Please make yourself comfortable with this idea as we're steadily
approaching the solution to your problem, and it will be based on this
concept. ;-)

> I also don't want to create a different branch (which I understand is
> what "git -stash" would do).

No, this is wrong.
`git stash` takes your local uncommitted changes (and also staged
changes -- those put into the index by `git add`-ing them, -- and also
untracked files if you excplicitly asked for his) and records them all
in a special form in a special area called "the stash".  No branch is
created, and there might be several entries in the stash which might be
accessed separately later.  Stashing the changes does not create a
branch.

> Right now, it seems the only thing I can do is un-doing my local
> changes, which sort of goes against the idea of having a repository.
> So: is there a way to merge the up-to-date "master code" unto my own
> local code without having to commit my code first or having to make
> branches?

The problem with your approach is that you propose a solution
with the set of ill-considered constraints and then ask about getting
it to work. ;-)

Well, OK, the simplest possible solution still within your constraints
is to use `git stash`:

  $ git stash         (1)
  $ git pull          (2)
  $ git stash pop     (3)

1) Will save away your changes and remove them from the work tree
   so that (2) is guaranteed to work (well, sort of but let's not
   digress).
2) Will bring the upstream's changes in.
3) Will attempt to patch the new state checked out files with your saved
   local changes.  If it will succeed, that stash entry will be deleted;
   otherwise it'll be kept and you'll be asked to resolve the conflicts.

This will work, I assure you, but let's also consider a more Git-way
approach.

First, it worth repeating that uncommitted changes in Git have different
"weight" than in centralized systems: in the latter, that's all you
may have locally compared to the server, while in Git you might have any
number of commits on any number of branches no other repository will
ever see or will see when you want it and no earlier.

Another, similar, idea applies to branches: the idea you have to bend
your brain around when coming to a DVCS system from a centralized one is
that all your local branches are also "truly yours" and their
connection to the same-named branches in another repository -- be it
"origin" or something else -- is only due to some policy and nothing
else.  And I mean it: once you have run `git clone` you're free to
change every single commit on that "master" branch you've got.  You're
free to rename it, free to replace it with another branch.  You can
simply delete it after all.  Neither of these changes will affect that
repository known as "origin" or any other foreign repository.  That is,
until you explicitly do something about this, like attempting to push
your munged branch "master" back into "origin". ;-)

Let's recap: your local branch "master" is quite effectively detached
from any other branch which happens to be named "master" in any Git
repository including "origin" it has been originally obtained from.

What this means for you, is that you're free to record any number of
commits on that branch it after cloning the repository, and unless you
explicitly push them back, nobody will ever know your branch "master"
has diverged from that in the "origin" repo.

Now let's deal with this divergence.  Once again recall that uncommitted
changes in a DVCS system are clearly a second-class citizen because
"the changes I'm not yet sent to the server" in a DVCS system is one or
more series of commits, not uncommitted changes.  There's another thing
to this: what if the size of your uncommitted changes grows too big?
They will simply get in the way.  It's much better to have them
organized into a series of (small and logical) commits on top of the
original branch.  And in fact that's what seasoned Git users
routinely do.

So let's restate your original problem this way: you would like to
maintain your local changes (in the form of one or more commits!) on
top of the branch "master" you'd like to periodically synchronize with
the development happening on it in the repository of its origin.

Enter `git rebase`.  This command has been originally developed
specifically for this task: to help you maintain a set of commits on
top of a branch which from time to time receives changes made to its
"upstream" copy.  `git rebase` can have quite a number of other
interesting applications but in the simplest case we're discussing it
takes the single argument: something onto which to rebase your current
branch including your local changes.  And where to take this "something"
from requires further explanations.

Even though all Git repositories are self-standing and do not require
other repositories to work properly, having steady exhange of commits
between your local repository and one or more external repositories is
so ubiquituos DVCSes provide helpers to carry out these grunt tasks.
Git's approach to this comes in the form of the so-called "remote
branches".  When you clone a repository, Git creates in your local
repository a single remote branch for each "normal" branch the source
repository had.  These branches are sort of bookmarks to the state of
the branches of that repository last time it was seen.  You never check
these branches out and never commit on them.  They get updated when you
run `git fetch` or when you successfully push.  You can see them by
running `git branch -r` ("r" is for "remote") or `git branch -a` ("a"
is for "all").  To refer to the remote branch representing "master" in
"origin" you use the name "origin/master".

Now let's put all these bits together.  Suppose you have added 5
commits with your local modifications on top of your local "master"
branch.  Clearly, your local "master" is now ahead of "origin/master"
by those 5 commits.  You can easily verify this by running

  git log origin/master..master

or

  git log master ^origin/master

Now you want to bring the upstream's changes back into local "master"
and still keep your local changes on top of it.
Let's first bring the upstream's changes in:

  git fetch

This invocation will update all the necessary remote branches including
"origin/master".  Your "master" is still intact.

You can now see what new changes the upstream's master has compared to
your local version of it:

  git log origin/master ^master

OK, now let's now rebase your local "master".  Recall that we needed to
have "something" which should represent upstream's version of our
branch to rebase onto?  That's what "origin/master" is there for!

  git rebase origin/master

This command first chops your 5 local commits off "master", then makes
"master" look exactly like "origin/master" and then applies your 5
local commits back to the result -- one by one starting from the first.
Should you have any conflicts during this phase, it will stop and
explain what to do next.

You can now verify your rebased "master" again is your 5 commits ahead
of "origin/master".

So let's recap:

1) We run `git fetch` to bring all the new stuff from the remote
   repository into ours and update the appropriate remote branches with
   that stuff.  It's now locally accessible.

2) We run a bunch of exploratory commands to understand what has
   happened upstream while we weren't following.

   This is not needed for trivial cases like yours but is a crucial
   bit of the workflow when you'll get serious with development.

3) We rebased our local branch onto its updated upstream state using
   the appropriate remote branch.

You might now consider reading the relevant material on remote branches
[1], and better read that whole book.

1. http://git-scm.com/book/en/v2/Git-Branching-Remote-Branches

-- 
You received this message because you are subscribed to the Google Groups "Git 
for human beings" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to git-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to