Fibo Kowalsky
Fibo Kowalsky

Reputation: 1248

Add an old version (out of version control) to the git repository as a tagged commit

I have a git repository for a system sys/master which I created after some time the system was created. I have a backup of the system sys_bk that was done before the repository creation.

What is the recommended way of adding the sys_bk version to the current repository as a tagged version without affecting the recent version of the system?

FYI, differences between sys_bk and sys/master are huge.

Upvotes: 0

Views: 46

Answers (2)

Mark Adelsberger
Mark Adelsberger

Reputation: 45659

I think the real unanswered question here is, how do you want this new(/old) commit to appear in history? Today you have a history that starts at some later date

D -- E -- F <--(master)

and now you have some older version A. Several users (jof in his answer, StephenNewell and torek in comments) have mentioned ways to add A to your repo. This gets you something like

D -- E -- F <--(master)

A <--(sys_bk)

Then StephenNewell suggests merging the unrelated histories, but really I doubt that's what you want to do. You specified not changing the current versions of the project, so at best it'd be a special type of evil merge. Essentially you'd end up with

D -- E -- F -- M <--(master)
              /
             A <--(sys_bk)

but the content (TREE) at M would not be the default/expected merge result for that history; rather it would be a duplicate of the TREE at F. So that makes a merge that could cause trouble down the line, and besides it doesn't reflect A's true place in the project history very well.

One option would be to just leave the histories unrelated. You could keep the sys_bk branch that you used to bring A into the repo, and/or you could tag A (which sounds like what you were planning to do), and it would be preserved for anyone that cares to look for it. (Well, visibility is probably simplest if you keep the branch rather than just a tag; depending on how you document what you're doing.)

Another reasonable thing to consider is making it so A looks like a parent of D. There are a couple ways to go about this, with pros and cons.

History Rewrite

Rewriting the history to physically wire A in as D's parent (roughly) is an option where you pay the cost up-front, and then everything works smoothly from then on. But depending how many people share your repo, the up-front cost could be quite large, and everyone pays it whether they care about A or not.

If you want to use this option, consult the docs for git filter-branch with the --parent-filter option. In the end you'd get something like

A -- d -- e -- f <--(master)

I'm using d to identify a new commit whose TREE is exactly like D. (Everything about it is like D, except it has a parent - A - whereas D had no parent.) This is slightly different than the typical "rewritten commit" from a rebase - especially in the case of d - which is why I'm using lower-case instead of prime notation. But the upshot is the same: these are new commits.

That means that, assuming master (and/or any other refs) have already been pushed to a remote, you'll have to either "force push" - git push -f - to overwrite those refs with the new ones, or just discard the remote and put a new one in its place. Either way, every other user will be put in a broken state which they'll have to correct carefully to avoid undoing what you've done (see "Recovering from Upstream Rebase" in the git rebase docs).

Object Replacement

A less invasive approach is to update individual repositories "as needed" to make them act as if A were a parent of D. In this case, anyone who's not concerned about seeing A as a parent of D is totally unaffected. The down side is that (a) it requires setup on each repo that wants it, and (b) it has some known bugs/quirks - see the git replace docs.

To facilitate this, you'd want to make a copy of commit D. You do this in a manner similar to the history rewrite, except you create a new branch to rewrite and leave all other branches alone.

So you'd work out an expression that refers to D. (Maybe you get its commit ID, or in our example case we could use master~2.)

git checkout master~2
git checkout -b sys_bk_replacement

Then run git filter-branch in much the same way as you would for a full rewrite, but specifying only the sys_bk_replacement branch (not --all as you probably would've for a full rewrite). This would give you

D -- E -- F <--(master)

A <--(sys_bk)
 \
  d <--(sys_bk_replacement)

and anyone wanting A to look like D's parent could use git replace to substitute d for D.

Upvotes: 1

jof
jof

Reputation: 314

The simplest approach that comes to mind would be to utilize git's git checkout --orphan <branch> command, such that you create a branch with no parent and the contents of your sys_bk folder as the initial commit.

An example workflow:

  • git checkout --orphan sys_bk (checkout a new branch with no parent)
  • git rm -rf . (remove all files from the working directory)
  • cp /path/to/sys_bk/* ./ (move sys_bk files into working directory)
  • git add --all . (add all new sys_bk files to index)
  • git commit -m "Add sys_bk"

Upvotes: 1

Related Questions