parsecer
parsecer

Reputation: 5106

Move existing file to another branch, put a new version of this file to master

I have a file Main.java and two versions of it: one version is located in my local and remote repositories, on branch master. The second version is located in another computer and changes in it have never been tracked.

I need to make the second file the master's version, while moving the first version to a new branch.

I'm not sure how to accomplish it, considering git never knew of the second version and changes in it never were commited in any form.

Upvotes: 0

Views: 153

Answers (1)

torek
torek

Reputation: 488213

All commits in any Git repository hold snapshots of files. Each commit holds a complete snapshot of all of the files that it holds.

That is, think of your Git repository as a collection of commits (because it is), with each commit strung together in a sort of backwards fashion. Let's draw a really simple repository with just three commits, and call those commits A, B, and C to make it easy to talk about them:

A <-B <-C

The last commit C, which is really identified by some big ugly hash ID—every commit has some big ugly hash ID, unique to that particular commit—remembers the hash ID of the second commit B. We say that C points to B, or that B is C's parent. The second commit B remembers the hash ID of commit A, so that B points to A.

Commit A is the very first commit we ever made. The repository was totally empty when we made it, so it doesn't point back anywhere else: it has no parent. There's always one commit like this in any non-empty repository.

Let's say we had files README.md and Main.java when we made commit A:

git init
git add README.md Main.java
git commit

That means commit A contains some version of README.md and Main.java. These snapshots, saved in commit A, are read-only (and compressed and Git-ified to save space and be read-only). Nothing can or will ever change anything at all about commit A: all parts of any commit are frozen for all time.

That's why B points to A, but A doesn't point to B, too: A is frozen now. We don't actually have B yet. Now we change Main.java or README.md, or make a whole new file. All of these changes don't happen in the commit at all: they happen in our work-tree, which Git gives to us to let us do our work. Let's say we make a new file, foo.doc, and git add it and git commit. That's what creates snapshot B: it has README.md—the same version of README.md as in snapshot A—and Main.java, again the same as in A, plus the new file foo.doc. It also remembers the hash ID of commit A.

Then we do something and make C, which remembers B. That's what gives us this backwards-looking chain of commits. All these commits are frozen for all time. As long as they still exist, the versions of the files that are saved with them, as their snapshots, are also saved for all time.

You have a Main.java that's not in this repository at all. You could just copy it into place, in the work-tree, and git add it and make a new commit:

A--B--C--D

and D would have the new version of Main.java. Note that commit C is still there, unchanged, frozen for all time.

The only tricky bit here is this: what exactly is a branch anyway? What does it mean for these commits to be "on" some branch(es)?

The answer is: a branch name—which is one of the things people sometimes mean when they say "a branch"—holds the hash ID of one commit. We say that the branch name points to that commit. Let's have master point to D:

A--B--C--D   <-- master

Now let's add another branch name, such as hello, but make it point to C:

A--B--C   <-- hello
       \
        D   <-- master

Nothing has changed in any of the commits! (I've drawn D on a new row, but that's just to squeeze in my arrows.) Commits A through D are now on master, and commits A through C are now on hello.

You can create and destroy branch names at any time, pointing to any commit that actually exists, using the git branch command. The easy way to get hello to point to C is to start with master pointing to C:

A--B--C   <-- master

and use git branch hello, which makes the new branch point to the same commit as the current branch / current commit:

A--B--C   <-- master, hello

We add the special name HEAD and attach it to one of these two branch names:

A--B--C   <-- master (HEAD), hello

to show which branch we have checked out. Both branch names identify commit C, so that git checkout master gets you commit C, and git checkout hello also gets you commit C, but the name HEAD remains attached to whichever name you used.

Now we make our new commit, by copying the file into place in our work-tree, then using git add, then git commit. The special trick about making a commit with git commit is that, after git commit makes the new commit, it changes the current branch name so that it points to the new commit:

A--B--C   <-- hello
       \
        D   <-- master (HEAD)

and we have just want we wanted.

Upvotes: 2

Related Questions