0

I am creating a module that ports a third party lib to another platform. The version of my module matches the version of the lib that it wraps.

For the purposes of this question, let's say that the lib is on version 10, therefore my module is also on version 10.

Git refs look like this:

master === HEAD === v10 (tag)

However, for compatibility reasons, I wish to create versions 8 and 9 of the wrapper as well.

If I commit v9, it will become the new HEAD, but I want HEAD to stay on v10. I just want v9 to be available option via

// switch to version 9
git checkout v9

Is there anyway to do this?

Note, I'm sort of a git noob. I've heard of rebasing and cherry picking, but I don't understand either of them very well. The accepted answer will provide a solution as well as an explanation.

Mulan
  • 129,518
  • 31
  • 228
  • 259
  • See [Git: how to add commits before first/initial/root commit?](http://stackoverflow.com/questions/16762160/git-how-to-add-commits-before-first-initial-root-commit). –  Aug 11 '13 at 21:01
  • For rebasing, see also [Git for beginners: The definitive practical guide](http://stackoverflow.com/questions/315911/git-for-beginners-the-definitive-practical-guide/5985070#5985070). I also ***highly recommend*** the [FREE online Pro Git book](http://git-scm.com/book), in particular chapters 1-3, 6-6.5. You can also see my profile for other Git resources. –  Aug 11 '13 at 21:33
  • See also [git rebase vs git merge](http://stackoverflow.com/questions/804115/git-rebase-vs-git-merge). –  Aug 11 '13 at 21:51
  • It's funny, whenever I search for certain types of questions about retroactively adding commits to a Git repo, this question keeps popping up. –  Jul 21 '14 at 23:42

1 Answers1

4

The question is a little unusual/unclear, mostly because it doesn't seem to be set in "The Git Way"/context of looking at source control. "Heads" in Git have more than one meaning...it doesn't mean the same thing like it does in SVN, for example. This is a point I will come back to later, but for now, I will attempt to address what seems to be the issue of adding previous versions of a code base to a Git repo.

How to retroactively add commits in Git

You have at least two options:

  1. Add previous versions of the code base as orphaned, separate branches. This is not something I recommend, because in my experience orphaned branches are inefficient to switch between in your working copy, probably because the checkout operation doesn't have access to common ancestors and stuff like that.
  2. You can add the previous versions as orphaned branches, but then rebase the newest v10/master branch on top of them, and add new tags.

I recommend you use option #2, since it's a problem that's been addressed on Stack Overflow before, and it's more of "The Git Way" to do solve this issue:

  1. git: how to insert a commit as the first, shifting all the others?
  2. Git: how to add commits before first/initial/root commit?)

In particular, I recommend you follow these basic steps that I give a detailed explanation of in this answer:

  1. Create a backup clone of your repo:

    git clone original-repo backup-repo
    
  2. Create an orphan branch (this will be your new root).

    git checkout --orphan new-master firstCommitOfOldMaster
    
  3. Remove working directory files from orphan branch.

    git rm -rf .
    
  4. Recommit older work into new-master.

  5. Rebase old master onto new-master.

    git rebase --onto new-master --root master
    
  6. Verify that the final commit is still the same.

    git diff master@{1}
    

Either before you rebase the old master onto the new master, or afterwards, you can tag your v9 branch with either a lightweight tag or an annotated tag:

# Lightweight
$ git tag "v9"

# Annotated
$ git tag -a "v9" -m "Tag version 9"

Explanation of HEAD

So "heads" in Git have several meanings, depending on context. In the context you're using it—as a commit reference—HEAD simply means the commit that you currently have checked out into your working copy. It does not mean the same thing as in SVN, i.e. HEAD does not represent the most recent global commit in a repository.

As you checkout between branches or commits along those branches, HEAD will point to different commits in your history (sometimes previous commits), because your're checking out different commits into your working copy. See

  1. What is git HEAD, exactly?.
  2. HEAD and ORIG_HEAD in Git.

There is another context in Git where "heads" are used: branches that are local to a repository are generally stored under the .git/refs/heads/ directory. In this context, "heads" just means branch pointers/references, and are the "tips"/most recent commit for a branch. You can see your local repo's branch heads/tips by running git show-ref:

git show-ref
35ec88f1c5b319b7ca08f5d3dca3cafdebddecd4 refs/heads/awesome-feature
ea112e91953e02f9c6dcc7b9470b8bdf0a69a3eb refs/heads/epic-feature
a9874fd23f67c245fadd23bef449971fa7338755 refs/heads/master

Here you can see that I have 3 branch heads/tips/references: the master branch, awesome-feature branch, and the epic-feature branch. To the left of each branch, you'll see the full commit sha that the branch currently points to, i.e. the most recent commit for that branch.

Explanation of rebase

First off, let me begin by saying that you should learn to rebase. Period. I argue that it's the most fundamental, essential, and powerful tool in Git, and that if you're not using it, then you're not using Git effectively. So learn to rebase, both interactively and non-interactively.

With that out of the way, I won't go into too much detail about how git rebase works, since there are plenty of online resources that explain it (I will list them), and this answer is already very very long.

But in a nutshell, rebase allows you to rewrite your history, in just about any way you could possibly want to. It takes a set of commits, and re-applies copies of those commits to wherever you want in your history. So that's what the solution I suggested above does, it takes your old v10 history, and recreates it on top of the new v9 history, as if that's the way the history was created all along.

Rebase resources

Here are excellent resources for learning more about rebase. Remember that you can always create a practice Git repo full of .txt files or whatever you want, and then practice branching, rebasing, and merging with it:

  1. Git Tools - Rewriting History.
  2. git-rebase(1) Manual Page.
  3. Learn Git Branching, see the Interactive Rebase demo.
  4. Code School course Git Real, lesson 6.
Community
  • 1
  • 1
  • +1. This makes great sense. I still have one remaining question though. Say I have v1 and v3 committed, but I want to add v2. Would I `branch` off of v1, `commit` v2, then `rebase` v3 on v2? Do I have to do anything to correct v4+ at this point? – Mulan Aug 11 '13 at 23:27
  • Also, if there are versions already pushed to a public repo, should I not `rebase` existing branches? How would I work around this limitation? – Mulan Aug 11 '13 at 23:30
  • @naomik regarding rebasing commits in a public repo, it's generally discouraged, because it forces people to re-sync any commits they have based off of the old history with the new history, which can sometimes be difficult. ***However***, if you can be sure that no one else has actually based any commits/work off of your old history, or you don't care if other people are forced to re-sync, then there's no ***technical*** limitation that would prevent you from rebasing public history. –  Aug 11 '13 at 23:43
  • @naomik if, on the other hand, you ***do care if other people are forced to re-sync work, then rebasing is not an option***. Off the top of my head, I don't know of any workaround solutions, but if I think of something, I'll add it as an answer. –  Aug 11 '13 at 23:46
  • @naomik regarding your first comment, I'll need some more context, because it's strange that your version tags aren't already set up such that you wouldn't need to rebase anything in the first place. How have you been managing versions/tags and branches in your repo? If it's public, may I have the url to view it? –  Aug 11 '13 at 23:55
  • the original "versions" were not part of my module's repository. I just wanted to start with the current version and provide older versions for people that needed a specific (older) version. – Mulan Aug 12 '13 at 00:02
  • Also, the lib that is being wrapped in my module has many versions. I didn't want to have to do all of this work on my own. I more-or-less wanted to provide a couple examples and then provide a guide for people to easily fork/pull-request in a specific version if it's not already supported. – Mulan Aug 12 '13 at 00:04
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/35241/discussion-between-naomik-and-cupcake) – Mulan Aug 12 '13 at 00:04
  • Note to self, I need to update the information in my answer to explain `HEAD` as a symbolic reference. –  Jul 21 '14 at 23:40