user2954463
user2954463

Reputation: 2401

Git wizard needed - How to merge a branch with a different directory structure?

Once upon a time, there were two branches:

--[stable]--hotfix1--h2--h3
       \
        \--[develop]--refactor1--r2--r3

I need to merge stable into develop, but I'm in a bit of a pickle.

develop has been refactored with a different directory structure. and Git is not able to follow the change.

(stable)
    |-app
    |  |-scripts
    |    |-site
    |      |-**ALL THE CODE

(develop)
    |-app
    |  |-modules
    |    |**ALL THE CODE

expected - When I run git merge, git applies the hotfixes in stable to the new file path in develop.

actual - Git marks the files with hotfixes as "deleted by us" because they have the old directory structure.

How can I get Git to apply the hotfixes in stable to the new directory structure in develop?

Upvotes: 0

Views: 320

Answers (2)

ephemient
ephemient

Reputation: 204668

You can help Git by simulating a merge with everything already renamed. Using Bash for example,

# Mark this as the branch to be merged.
git update-ref --stdin <<<'create MERGE_HEAD stable'
printf '%s\t\t%s\n' $(git rev-parse MERGE_HEAD) "branch 'stable'" |
>$(git rev-parse --git-dir)/MERGE_MSG git fmt-merge-msg \
  $([[ $(git config --bool merge.log) = true ]] && echo --log)
# This outputs a tree object with some paths transformed.
refactor-tree() {
  (
    index1= index2=
    trap 'rm -f "${index1}" "${index2}"' 0
    index1=$(mktemp) index2=$(mktemp)
    GIT_INDEX_FILE=${index1} git read-tree "$@"
    GIT_INDEX_FILE=${index2} git read-tree --empty
    GIT_INDEX_FILE=${index1} git ls-files -s -z |
    while IFS=$'\t' read -d '' -r info file; do
      [[ ${file} = app/scripts/site/* ]] && file=app/modules/${file#*/*/*/}
      printf '%s\t%s\0' "${info}" "${file}"
    done |
    GIT_INDEX_FILE=${index2} git update-index --index-info
    GIT_INDEX_FILE=${index2} git write-tree
  )
}
# Update the index for the 3-way merge.
git read-tree -m --aggressive -u \
  $(refactor-tree $(git merge-base MERGE_HEAD HEAD)) \
  $(git write-tree) \
  $(refactor-tree MERGE_HEAD)
# Resolve the merge.
git merge-index -o git-merge-one-file -a
# Commit the results.
git commit

The above was tested on a demo repository created like this:

git init
mkdir -p app/scripts/site
cat >app/scripts/site/README.md <<EOF
Hello, world!
=============
EOF
git add app/scripts/site/README.md
git commit -m 'base'
git checkout -b stable
sed -i -e '1s/w/W/' app/scripts/site/README.md
git add app/scripts/site/README.md
git commit -m 'hotfix'
git checkout master -b develop
git mv app/scripts/site app/modules
rm -rf app/scripts
git commit -m 'refactor'
cat >>app/modules/README.md <<EOF
blah blah blah
EOF
git add app/modules/README.md
git commit -m 'blah'

Notice that if you remove the ============= line, the merge will fail due to a conflict in that file, but it gets context markers like usual. You can resolve it by hand and try to commit again.

Upvotes: 1

instantepiphany
instantepiphany

Reputation: 179

It might work to just change the directory structure for stable to match develop (in a commit), you could then just merge them together. I would just do this manually (with a file browser), but if you are really itching to use git commands for the whole task, you could find the commit on develop that changed the directory structure, cherry-pick that onto stable, and then the merge would probably apply properly (with some merge conflicts possibly).

Upvotes: 1

Related Questions