1

I created a new feature branch and added a file there. Every commit after creating that branch only targets this file.
Now I want to move those commits to a new repository that is just for this new file.
How do I achieve that? I tried to add the old repository as remote branch to the new repository and cherry-pick all commits but I also get older commit messages from before the feature branch and from other files and I lose the original commit dates.

So this is what I need:

  • copy/move a range of commits from one repo to another
  • only the commits of that file beginning from creating that file to today (there are no other files involved in between, but before creating the old feature branch)
  • keep original authors and commit dates

What I've also tried:

# git rebase --onto develop oldRepo/feature/issue#14 c7bc952

where c7bc952 is the current HEAD.

What I got:

First, rewinding head to replay your work on top of it...

# git status
HEAD detached from 4ae31a1
nothing to commit, working directory clean
Oliver Kötter
  • 1,006
  • 11
  • 29

2 Answers2

2

Major surgery like this is best done by directly constructing the new history. I like to do work like this in a scratch clone:

git clone -s . $TMPDIR/wip         # scratch clone
cd !$

Now to rewrite all of history to include only commits affecting that file, and only that file from those commits:

git filter-branch --index-filter '
                        git read-tree --empty
                        git reset $GIT_COMMIT -- that/file
                ' \
        -- --all -- that/file

If you want just one branch, use e.g. master instead of --all above.

Now you can push the rewritten branches anywhere you want. Remember that "origin" in this repo is the repo you cloned from.


As a quick recap:

  • a working repository comes in four parts: object db, refs, index, worktree. The HEAD ref conventionally identifies the commit that produced the current index and worktree; git commit uses it as the new commit's first parent, git checkout switches it out to the new branch, and so on.

  • the index is just a list: pathname, content id, metadata about inflight ops. Git status, for instance, checks the HEAD ref to see what was committed at a path, the index to see what you last added or checked out there, and the worktree to find any unstaged aka unadded aka untracked aka unindexed content.

  • the refs are just thumbs into the object db.

So, git read-tree is what loads the index from the object db (and, if you ask it to, the worktree). It's the fundamental operation underlying much of what you do with git.


So filter-branch invokes the filters you supply on everything rev-list produces from your args (the stuff after the first --). Here, I'm supplying --all -- that/file, so rev-list supplies every commit that touches that/file. When you supply an index filter, filter-branch loads the index from the commit, sets GIT_COMMIT, runs your filter, and rewrites the commit using the resulting index state.

Here, git read-tree --empty; git reset $GIT_COMMIT -- that/file empties the index and then loads only the that/file entry from the current commit. Lather, rinse, repeat.

jthill
  • 55,082
  • 5
  • 77
  • 137
  • It works fine. I have a rough idea how it works, but is there any explanation available how this works? I tried to read the docs for filter-branch and read-tree but this is too high for me at the moment. But I want to learn! – Oliver Kötter May 09 '16 at 09:46
1

I answer my question because this is the first solution I found, maybe it is helpful for others:

git rebase hash_of_parent_of_first-commit hash_of_last_commit --onto develop --committer-date-is-author-date

This rebases all commits from my current feature branch to my before empty develop branch.

Oliver Kötter
  • 1,006
  • 11
  • 29