Really flatten a git merge

There’re few question about “flattening merge” on StackOverflow, with an answer usually being “git rebase“. These answers though miss one crucial point – order of commits.

Suppose there’s a branch A with commits of Jun 1 and Aug 1, and branch B with commit of Jul 1 (UPDATE to reinstate the usecase described below: branches are fully independent and don’t have common ancestry, for example coming from 2 different repositories). When merging B into A, there will be following history (per git log):

  • managing ssh public keys on github organizations repositories
  • Git log: excluding commits from a specific merge
  • Git: Where exactly is the “working directory”?
  • Git error 403 pushing
  • How to keep git from changing file ownership
  • How to undo the initial commit on a remote repository in git?
  • Merged branch 'B'
    Aug 1
    Jul 1
    Jun 1
    

    Now, what I’m looking for is the way to get the same result, but without merge commit (and thus with underlying linear history in that order, and yes, that means re-parenting commits). git rebase doesn’t help here, as with it, you will get following histories:

    Jul 1
    Aug 1
    Jun 1
    

    or

    Aug 1
    Jun 1
    Jul 1
    

    In other words, git rebase always stacks one branch on top of another, while I’m looking for solution which will intersperse commits sorted by author’s commit date.

    Apparently, for simple cases, needed arrangement can be achieved by manually postprocessing git rebase with git rebase -i, but that’s not practical for large histories, so I’d be looking for automated command/script.

    Usecase? If A and B represent different parts of the same project which happened to be in different repos and time has come to correct that by merging them together, then it’s natural to want the linear history unfolding in the actual order of development.

  • Git push results in “fatal: sha1 file '<stdout>' write error: Invalid argument” error
  • List all text (non-binary) files in repo
  • Deploying a git branch together with its submodule from Github to Heroku
  • error: commit is a merge but no -m option was given
  • Why does a merge need you to consider the common ancestor?
  • Merge/Branch Strategy
  • 4 Solutions collect form web for “Really flatten a git merge”

    After some thinking, I figured out how to do How do I run git rebase –interactive in non-interactive manner? , which also provides completely scripted solution for this question.

    1. Bring 2 branches from different repositories into one repository (git remote add + git fetch)

    2. Rebase (non-interactively) one branch on top of another (order matters, consider first commit of which branch you’d like to have as first commit of consolidated branch).

    3. Prepare following script (rebase-reoder-by-date):

    #!/bin/sh
    awk '
    /^pick/ {
                printf "%s %s ", $1, $2;
                system("echo -n `git show --format='%ai' -s " $2 "`");
                for (i = 3; i <= NF; i++) printf " %s", $i; printf "\n";
            }
    ' $1 | sort -k3 > $1.tmp
    mv $1.tmp $1
    

    4. Run: GIT_SEQUENCE_EDITOR=./rebase-reoder-by-date git rebase -i <initial commit>

    Disclaimer: all these operations should happen on copies of original repositories, review/validate/test combined branch to make sure it is what you expected and contains what you expect, keep backups handy.

    What is the problem with leaving seperate development in seperate lines up until they were merged? If they were seperate, then they were seperate.

    There are many ways to view the history in chronological order without hacking the history as you’re trying. Have you tried git log --pretty --date-order?

    [See my another answer for completely automated solution. I’d leave this as an example of path which led for ultimate solution, in case someone will face similar not-so-obvious to solve task.]

    Ok, this is not real answer to the question (fully scripted, automated solution), but thinking and example how (interactive rebase based) processing can be automated.

    Well, first of all, for the ultimate solution git filter-branch --parent-filter looks like exactly what’s needed. Except that my git-fu doesn’t allow me to wrote, 1-, 2-, or 3-liner with it, and approach to write standalone script to parse thru all revisions is not cool and more effortful than rebase -i.

    So, rebase -i could be used efficiently if author dates of commit were visible. My first thought was to temporarily patch commit messages to start with author date using git filter-branch --msg-filter, run rebase -i, then unpatch messages back.

    Second thought though was: why bother, better to patch rebase commit list as used by rebase -i. So, the process would be:

    1. Bring branches A and B from different repos into one repo, as usual.
    2. Rebase (non-interactively) one branch on another. Consider which branch should be rebased on which, to have initial commit right (which cannot be easily rewritten with rebase).
    3. Start git rebase -i
    4. In another console, go to $REPO/.git/rebase-merge/
    5. Run: awk '/^pick/ {printf "%s %s ", $1, $2; system("echo -n git show --format='%ai' -s " $2 ""); for (i = 3; i <= NF; i++) printf " %s", $i; printf "\n"; }' git-rebase-todo > git-rebase-todo.new; mv git-rebase-todo.new git-rebase-todo
    6. This seems just the right place/way to reorder commits either: sort -k3 git-rebase-todo >git-rebase-todo.new; mv git-rebase-todo.new git-rebase-todo
    7. Switch to original console and reload git-rebase-todo file in editor, then exit editor.

    Voila! Actually, this could be completely scripted if git rebase -i could work in “non-interactive” mode, I submitted How do I run git rebase –interactive in non-interactive manner? for that.

    Actually, If I understand correctly, you can achieve this easily with git-stitch-repo.

    Git Baby is a git and github fan, let's start git clone.