6

I'm building a retrospective project history from zip snapshots. I have already built a long branch sequence of commits from snapshots that were to hand. I have now added at the end some more 'found' snapshots that should be at various places 'in the middle' of the commit sequence.

I'm trying to use the git rebase -i <startCommit> to re-order the git snapshots. I simply swap around the pick list order. This should be simply a case of re-writing the commit objects, but keeping the underlying trees the same (because the snapshots haven't changed).

It looks like rebase, in this case, is still trying to create patches and having lots of conflicts, rather than doing the simple re-arrangement. Is there a more appropriate command for this particular case? I have no merges, and only the one branch. The whole repo is still very local and private.

Philip Oakley
  • 13,333
  • 9
  • 48
  • 71
  • 1
    `git rebase` assumes that commits are actually identified by their changes, not just by the tree. What you are trying to do is quite unusual, so I think the porcelain part of git won't be much help for you and you'll have to use plumbing. – svick Aug 05 '11 at 23:32
  • I'm wondering if using [this custom merge driver](http://stackoverflow.com/questions/928646/how-do-i-tell-git-to-always-select-my-local-version-for-conflicted-merges-on-a-sp/930495#930495) or similar would work. – Karl Bielefeldt Aug 06 '11 at 03:37
  • @Karl: I'll have a good look through that method to at least understand what it is doing. The quick look suggests it might squeak ahead of Adymitruk's method if it simplifies my Windows<->Linux confusions;-) – Philip Oakley Aug 06 '11 at 07:12
  • I've flagged Adam Dymitruk's answer as the best, but see the comments below the fold. – Philip Oakley Aug 12 '11 at 20:44

4 Answers4

2

don't bother with rebase. Make a new branch. If X is sha1 or treeish of commit to use, then:

git checkout X -- .
git add -A
git commit -C X

Repeat for all commits in the chronological order that you want.

An example where we reverse the order of the last 5 commits:

git checkout -b temp old_branch~5
git checkout old_branch -- .
git add -A
git commit -C old_branch
git checkout old_branch~1 -- .
git add -A
git commit -C old_branch~1
git checkout old_branch~2 -- .
git add -A
git commit -C old_branch~2
git checkout old_branch~3 -- .
git add -A
git commit -C old_branch~3
git checkout old_branch~4 -- .
git add -A
git commit -C old_branch~4

git push . HEAD:old_branch -f
git checkout old_branch
git branch -d temp

git log -5 # to see the new history

hope this helps.

Adam Dymitruk
  • 124,556
  • 26
  • 146
  • 141
  • I think I might go along with this one, but with the twist of using the initial rebase to get the list of commit sha1s, mainly because it puts them all in the pick file. Then run it as a script. Its awkward to learn some of the linux editing & script methods after 30 years on other OSs ! – Philip Oakley Aug 06 '11 at 07:08
  • Should the `git add -A` commands have a trailing `.` to select all the the files, or is it an implicit part of `-A`? (I've always seen / used it) – Philip Oakley Aug 06 '11 at 07:32
  • I'm also getting a problem with the failure of the sequence to remove dropped directories and files between the checkouts. The top level directory name changes between each snapshot, which would normally show as a set of renames, but here I'm simply getting an additive set! Any suggestions? – Philip Oakley Aug 06 '11 at 22:37
  • Re-reading the checkout man page, could the effect be because I am using the sha1 values, rather than the branch~depth method of selecting the commit? [i.e. detached vs non-detached] – Philip Oakley Aug 06 '11 at 22:58
  • git add -A should be done from the root of repo and you do not need the '.'. – Adam Dymitruk Aug 08 '11 at 15:27
  • thank you for the clarification. [I've been away 4 days]. I'll check it out and see if I can get it to work as expected. – Philip Oakley Aug 12 '11 at 11:49
  • unfortunately it isn't working as expected when there is a change in the top level directory. The checkout leaves the old TLD present and adds the changed name directory by its side, so I have both old and new content. It is almost as if I need a `git rm -f .` to empty the working directory before the checkout – Philip Oakley Aug 12 '11 at 16:04
  • Perhaps. I wrote all that from memory. Let me know what the exact step is to reproduce the issue. – Adam Dymitruk Aug 12 '11 at 17:35
  • I appear to have got it now. I have created an alias in my config file `reorder = "!sh -c 'git rm -fr .; git checkout $1 -- .; git add -A; git commit -C $1;' -"` which concatenates the four steps. It also includes the `-r` recursive option in the initial content removal. It does surprise me that checkout and commit cannot be made complementary though. – Philip Oakley Aug 12 '11 at 20:42
  • you could use the lower level git commands to just reference the trees from the other commits. That would be the easiest actually although not the most intuitive to most. – Adam Dymitruk Aug 12 '11 at 21:52
  • A final point: I found that I had two identical [content] commits, so I used the `--allow-empty` commit option so that 'management' would be able to see the fully captured history from _all_ the zip files;-) – Philip Oakley Aug 13 '11 at 10:02
0

I can't help make the rebase easier, but I can explain why it isn't a "simple re-arrangement." As an example, take one file that has one line of text appended per revision. On the first commit, it looks like:

Commit 1

On the second commit, since you originally put them out of order, it looks like:

Commit 1
Commit 2
Commit 3

On the third commit, which was your second snapshot, it looks like:

Commit 1
Commit 2

Git is dealing with patches, so to git, the series of commits looks like:

  • Add a file with a line that says "Commit 1"
  • Add lines that say "Commit 2" and "Commit 3" after the line that says "Commit 1"
  • Remove the line that says "Commit 3" after the line that says "Commit 2"

When you reorder it, it looks like:

  • Add a file with a line that says "Commit 1"
  • Remove the line that says "Commit 3" after the line that says "Commit 2"
  • Add lines that say "Commit 2" and "Commit 3" after the line that says "Commit 1"

It gets to the second step, sees no lines with "Commit 2" to match against, doesn't know what to do, so requires a manual merge.

You manually resolve the merge, then on the next step, git sees 2 lines where it was expecting one, and again requires a manual merge.

Karl Bielefeldt
  • 47,314
  • 10
  • 60
  • 94
  • That's why I think `git rebase` is not the right tool for this job. I'm not sure what exactly is though. – svick Aug 06 '11 at 00:45
  • This would be very tedious. You don't want to apply a patch but instead just stitch together the snapshots present at each commit. – Adam Dymitruk Aug 06 '11 at 01:58
0

If your history looks like this:

a->b->c->d->e->f

Where e and f are supposed to be placed somewhere in between a and d

You can still do the rebase -i, but put editon the commit before where you want the snap shot inserted. When git pauses for that edit, place the snapshot in your working dir and add/commit it. Continue the rebase till the next spot, rinse, repeat.

In case it needs to be said, you will also want to remove the initially misplaced commits, e and f, that deal with the misplaced snap shots.

Andy
  • 44,610
  • 13
  • 70
  • 69
  • That will still probably cause lots of conflicts. And it will try to apply the changes, so the result may not be the exact same tree as before. – svick Aug 06 '11 at 01:20
0

Tried a lot of options and answers, checkouts and resets during re-base conflict instead of manual fixes. Tried to do this without manual steps and without workarounds. However, most intuitive and transparent way to prevent git from interfering your files appears to be an empty commit:

Trees

Let's say task is to insert v3 into history:

  • go to v5, do clean checkout, do manual backup of v5
  • go to v3, delete all files, commit empty
  • start re-base (main to master)
  • if case is complicated (which is supposed if this SO question was found), conflict will occur immediately; Unstage all files, delete, copy from v5 backup, stage all, add ignored; Before re-base continues, ensure that project folder is equal to v5 backup folder
  • continue re-base (changing commit messages may be necessary?)

Still had errors in the end, but checkouts were finally same.

halt9k
  • 527
  • 4
  • 13
  • If you have a new question, please ask it by clicking the [Ask Question](https://stackoverflow.com/questions/ask) button. Include a link to this question if it helps provide context. - [From Review](/review/late-answers/32428478) – OznOg Aug 12 '22 at 09:50