1

So for the past few days, I have been trying to figure out why my git commit history looks something like this where my commit history is not linear like I would like it to be? I have tried many things to try to get this so called flattened out but to no avail.

If anybody out there could help that would be greatly appreciated.

View from git log --oneline --graph command in git bash

view 1

View from git log with in SmartGit

view 2

Here is the error that I got after I ran git rebase 0fa9983894820ac8062a7413188e4ea2bc28089b

error

Dom
  • 1,687
  • 6
  • 27
  • 37
Alex Lowe
  • 783
  • 3
  • 20
  • 43
  • It looks like that because you did a merge between one branch and another. It shows the git history of what you did. Did you want to change your git history to look like a series of commits, or did you want to know how to avoid that in the future? – Bryce Drew Jun 27 '16 at 22:24
  • @BryceDrew If you could show me both that would be awesome!!! – Alex Lowe Jun 27 '16 at 22:26
  • If you do a git log --decorate --all, you should see the branch/tag names and that could help identify a branch for the 051' or 901' commits, to help see how they came about. – David Neiss Jun 27 '16 at 22:32
  • 1
    That did not seem to help me pin point the problem. – Alex Lowe Jun 27 '16 at 22:36
  • 1
    I regret that I don't have the time to be too thorough right now. [Learn when to rebase instead of Merg](http://stackoverflow.com/q/804115/6194839). If you are unawsered by tommorow I will show you. – Bryce Drew Jun 27 '16 at 22:36
  • Ok that sounds great. Thanks for your help so far. – Alex Lowe Jun 27 '16 at 22:39
  • 1
    Bryce linked an excellent resource. Note especially this [comment](https://stackoverflow.com/questions/804115/when-do-you-use-git-rebase-instead-of-git-merge#comment-20229547), which should help you avoid this in the future. – jpaugh Jun 27 '16 at 22:57

3 Answers3

3

Check out my ugly tree:

* 8d61057 (HEAD -> master) file4
*   61795ae Merge branch 'new'
|\  
| * 56f104d file2
* | 2484695 file3
|/  
* ecc3f4f init

Notice that 61795ae Merge branch 'new' introduces nothing new, but is merely a merge commit.

I can get my trunk back in a straight line with rebase from the last commit before the split.

git rebase ecc3f4f

Now the tree is all straight again

* e794681 (HEAD -> master) file4
* b65595a file3
* 3d6ffe9 file2
* ecc3f4f init

Explanation

After cloning your project, I wanted to take a look at the commits to see the conflict.

You renamed this backup script.

051dae3 Rename backup final ftp.py to backup ftp v.4 (final).py
Backup scripts/backup final ftp.py → Backup scripts/backup ftp v.4 (final).py

But at the same time in another clone of this repository, you moved this backup script into a "Python" folder with a group of others

09abfd7 Renamed some of the files
Backup scripts/backup final ftp.py → Python/Backup scripts/backup final ftp.py

Now to resolve this conflict, you had to clean it up with two commits.

The first one just to get the file out of the right folder to the wrong folder with the right name.

8006e70 Renamed some files
Python/Backup scripts/backup final ftp.py → Backup scripts/backup ftp v.4 (final).py

Then you just lost that file with the "unneeded folder"

bac2105 Deleted an unneeded folder
deleted Backup scripts/backup ftp v.4 (final).py

Basically, it was a matter of checking out those two commits, resetting them softly to the staging area, and committing them all in one.

Then cherry-pick the rest. You can clone the straight master branch here

Procedure

git clone https://github.com/Lowe-Man/Portfolio

cd Portfolio/

git log --graph --oneline --decorate --all
* de71d41 (HEAD -> master, origin/master, origin/HEAD) Removed a gitignore specifier from the .gitignore file
* f80eb5c Added 'Lecture 26 Files'
* 505478e Added 'Lecture 25 Tuples'
* 632a2f3 Added 'Lecture 23 Dictionaries'
* 990d74e Reorganized/Fixed all of the code/comments on all of the lectures that I have done up to this point
* bb213ac Added 'Lecture 21 Lists'
* ab99b1c Added 'Lecture 19 Print Formatting'
* 3f37ca3 Changed the numbering of the files
* 16e17be Added 'Lecture 16 Strings'
* c654494 Reorganized the repository
* 60b17e4 Added 'Remove Numbers v.1.py'
* e35b6ba Added 'Lecture 14 Numbers'
* bac2105 Deleted an unneeded folder
*   8006e70 Renamed some files
|\  
| * 051dae3 Rename backup final ftp.py to backup ftp v.4 (final).py
* | 09abfd7 Renamed some of the files
|/  
* 0fa9983 Everything is in working order
* dfc9747 Fixed a few if statements
* e183900 Added Countdown Timers and other help to the portfolio
* 2377980 Removed some unnecessary lines of code
* 4172115 Added some new fetures to the script
* 4a7f2d1 Fixed some minor bugs in the code
* 589251c Removed text from a variable
* 93e9742 Finished the backup final ftp script for now
* c5687a3 Added the feature to remove files/folders that are older than a certain age
* 19a495f Removed some blank lines at the bottom of the file
* c2c58c3 Converts the folder to a .tar.bz2 and then deletes the folder
* 6718968 Made some minor changes to the code to increase usability
* b7d6608 Initial commit
git checkout 051dae3

git reset --soft HEAD~

git status
HEAD detached from 051dae3
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

  renamed:    Backup scripts/backup final ftp.py -> Backup scripts/backup ftp v.4 (final).py
git checkout 09abfd7

git reset --soft HEAD~

git status
HEAD detached from 09abfd7
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

  new file:   Backup scripts/backup ftp v.4 (final).py
  renamed:    Backup scripts/backup final ftp.py -> Python/Backup scripts/backup final ftp.py
  renamed:    Backup scripts/backup ftp v.1.py -> Python/Backup scripts/backup ftp v.1.py
  renamed:    Backup scripts/backup ftp v.2.py -> Python/Backup scripts/backup ftp v.2.py
  renamed:    Backup scripts/backup ftp v.3.py -> Python/Backup scripts/backup ftp v.3.py
  new file:   Python/Backup scripts/backup local.py
  renamed:    Countdown Timers/Countdown Timer (Cleaned).py -> Python/Countdown Timers/Countdown Timer v.2.py
  new file:   Python/Countdown Timers/Countdown Timer v.3 (final).py
  renamed:    Countdown Timers/Coutdown Timer.py -> Python/Countdown Timers/Coutdown Timer (original).py
  renamed:    Countdown Timers/Coutdown Timer (Improved).py -> Python/Countdown Timers/Coutdown Timer v.1.py
  renamed:    Help/How to remove zeros.py -> Python/Help/How to remove zeros.py
  renamed:    Help/Timezone.py -> Python/Help/Timezone.py
  renamed:    Countdown Timers/length.py -> Python/Help/length.py

Ditch the extra copy, and move the correctly named one into the Python folder.

rm 'Python/Backup scripts/backup final ftp.py'

mv 'Backup scripts/backup ftp v.4 (final).py' 'Python/Backup scripts/backup ftp v.4 (final).py'

git add .

git status
HEAD detached from 09abfd7
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

  renamed:    Backup scripts/backup ftp v.1.py -> Python/Backup scripts/backup ftp v.1.py
  renamed:    Backup scripts/backup ftp v.2.py -> Python/Backup scripts/backup ftp v.2.py
  renamed:    Backup scripts/backup ftp v.3.py -> Python/Backup scripts/backup ftp v.3.py
  renamed:    Backup scripts/backup final ftp.py -> Python/Backup scripts/backup ftp v.4 (final).py
  new file:   Python/Backup scripts/backup local.py
  renamed:    Countdown Timers/Countdown Timer (Cleaned).py -> Python/Countdown Timers/Countdown Timer v.2.py
  new file:   Python/Countdown Timers/Countdown Timer v.3 (final).py
  renamed:    Countdown Timers/Coutdown Timer.py -> Python/Countdown Timers/Coutdown Timer (original).py
  renamed:    Countdown Timers/Coutdown Timer (Improved).py -> Python/Countdown Timers/Coutdown Timer v.1.py
  renamed:    Help/How to remove zeros.py -> Python/Help/How to remove zeros.py
  renamed:    Help/Timezone.py -> Python/Help/Timezone.py
  renamed:    Countdown Timers/length.py -> Python/Help/length.py

In addition to everything else we renamed, the staged resolution of interest is:

  renamed:    Backup scripts/backup final ftp.py -> Python/Backup scripts/backup ftp v.4 (final).py

Make a new commit using the same message.

git commit -c ORIG_HEAD

git log --graph --oneline --decorate --all
* 1cc1b2c (HEAD) Renamed some of the files
| * de71d41 (origin/master, origin/HEAD, master) Removed a gitignore specifier from the .gitignore file
| * f80eb5c Added 'Lecture 26 Files'
| * 505478e Added 'Lecture 25 Tuples'
| * 632a2f3 Added 'Lecture 23 Dictionaries'
| * 990d74e Reorganized/Fixed all of the code/comments on all of the lectures that I have done up to this point
| * bb213ac Added 'Lecture 21 Lists'
| * ab99b1c Added 'Lecture 19 Print Formatting'
| * 3f37ca3 Changed the numbering of the files
| * 16e17be Added 'Lecture 16 Strings'
| * c654494 Reorganized the repository
| * 60b17e4 Added 'Remove Numbers v.1.py'
| * e35b6ba Added 'Lecture 14 Numbers'
| * bac2105 Deleted an unneeded folder
| *   8006e70 Renamed some files
| |\  
| | * 051dae3 Rename backup final ftp.py to backup ftp v.4 (final).py
| |/  
|/|   
| * 09abfd7 Renamed some of the files
|/  
* 0fa9983 Everything is in working order
* dfc9747 Fixed a few if statements
* e183900 Added Countdown Timers and other help to the portfolio
* 2377980 Removed some unnecessary lines of code
* 4172115 Added some new fetures to the script
* 4a7f2d1 Fixed some minor bugs in the code
* 589251c Removed text from a variable
* 93e9742 Finished the backup final ftp script for now
* c5687a3 Added the feature to remove files/folders that are older than a certain age
* 19a495f Removed some blank lines at the bottom of the file
* c2c58c3 Converts the folder to a .tar.bz2 and then deletes the folder
* 6718968 Made some minor changes to the code to increase usability
* b7d6608 Initial commit

Now we've effectively combined commits 051dae3, 8006e70, and bac2105 into 09abfd7 as a new commit 1cc1b2c which is the way I think it should have functionally been committed in the first place.

So all that's left is to cherry-pick the rest of the tree starting from e35b6ba Added 'Lecture 14 Numbers'

git cherry-pick e35b6ba^..de71d41

git log --graph --oneline --decorate --all
* 0e52036 (HEAD) Removed a gitignore specifier from the .gitignore file
* 1cf948b Added 'Lecture 26 Files'
* 62a1602 Added 'Lecture 25 Tuples'
* aeb7421 Added 'Lecture 23 Dictionaries'
* d47635d Reorganized/Fixed all of the code/comments on all of the lectures that I have done up to this point
* d54141c Added 'Lecture 21 Lists'
* 66174f2 Added 'Lecture 19 Print Formatting'
* 3969da2 Changed the numbering of the files
* 9c4828c Added 'Lecture 16 Strings'
* ce159a3 Reorganized the repository
* 759e08b Added 'Remove Numbers v.1.py'
* 323be27 Added 'Lecture 14 Numbers'
* 1cc1b2c Renamed some of the files
| * de71d41 (origin/master, origin/HEAD, master) Removed a gitignore specifier from the .gitignore file
| * f80eb5c Added 'Lecture 26 Files'
| * 505478e Added 'Lecture 25 Tuples'
| * 632a2f3 Added 'Lecture 23 Dictionaries'
| * 990d74e Reorganized/Fixed all of the code/comments on all of the lectures that I have done up to this point
| * bb213ac Added 'Lecture 21 Lists'
| * ab99b1c Added 'Lecture 19 Print Formatting'
| * 3f37ca3 Changed the numbering of the files
| * 16e17be Added 'Lecture 16 Strings'
| * c654494 Reorganized the repository
| * 60b17e4 Added 'Remove Numbers v.1.py'
| * e35b6ba Added 'Lecture 14 Numbers'
| * bac2105 Deleted an unneeded folder
| *   8006e70 Renamed some files
| |\  
| | * 051dae3 Rename backup final ftp.py to backup ftp v.4 (final).py
| |/  
|/|   
| * 09abfd7 Renamed some of the files
|/  
* 0fa9983 Everything is in working order
* dfc9747 Fixed a few if statements
* e183900 Added Countdown Timers and other help to the portfolio
* 2377980 Removed some unnecessary lines of code
* 4172115 Added some new fetures to the script
* 4a7f2d1 Fixed some minor bugs in the code
* 589251c Removed text from a variable
* 93e9742 Finished the backup final ftp script for now
* c5687a3 Added the feature to remove files/folders that are older than a certain age
* 19a495f Removed some blank lines at the bottom of the file
* c2c58c3 Converts the folder to a .tar.bz2 and then deletes the folder
* 6718968 Made some minor changes to the code to increase usability
* b7d6608 Initial commit

Now we can delete our old master branch, use this one as the new master, and force push to the remote.

git branch -d master

git checkout -b master

git push origin master --force

git log --graph --oneline --decorate --all
* 0e52036 (HEAD -> master, origin/master, origin/HEAD) Removed a gitignore specifier from the .gitignore file
* 1cf948b Added 'Lecture 26 Files'
* 62a1602 Added 'Lecture 25 Tuples'
* aeb7421 Added 'Lecture 23 Dictionaries'
* d47635d Reorganized/Fixed all of the code/comments on all of the lectures that I have done up to this point
* d54141c Added 'Lecture 21 Lists'
* 66174f2 Added 'Lecture 19 Print Formatting'
* 3969da2 Changed the numbering of the files
* 9c4828c Added 'Lecture 16 Strings'
* ce159a3 Reorganized the repository
* 759e08b Added 'Remove Numbers v.1.py'
* 323be27 Added 'Lecture 14 Numbers'
* 1cc1b2c Renamed some of the files
* 0fa9983 Everything is in working order
* dfc9747 Fixed a few if statements
* e183900 Added Countdown Timers and other help to the portfolio
* 2377980 Removed some unnecessary lines of code
* 4172115 Added some new fetures to the script
* 4a7f2d1 Fixed some minor bugs in the code
* 589251c Removed text from a variable
* 93e9742 Finished the backup final ftp script for now
* c5687a3 Added the feature to remove files/folders that are older than a certain age
* 19a495f Removed some blank lines at the bottom of the file
* c2c58c3 Converts the folder to a .tar.bz2 and then deletes the folder
* 6718968 Made some minor changes to the code to increase usability
* b7d6608 Initial commit
Jeff Puckett
  • 37,464
  • 17
  • 118
  • 167
  • I edited my question with the error message that I got after I ran `git rebase `. – Alex Lowe Jun 27 '16 at 23:42
  • Those two commits renamed the same file, so you have to tell git how to resolve the conflict. See the other answer for a more detailed explanation – Jeff Puckett Jun 28 '16 at 00:01
  • I can't figure out how to fix this from the information in the answer below. – Alex Lowe Jun 28 '16 at 00:09
  • You can get back to where you were in sync with the remote with `git reset --hard origin/master` then checkout that commit before the split, make a new branch from there, cherry pick the commits you want, and you can grab a range of commits with `..` notation – Jeff Puckett Jun 28 '16 at 01:02
  • That didn't really work because my repo is messed up and/or I have never encountered this issue before. But [here](https://github.com/Lowe-Man/Portfolio) is the repo if you want to try it for yourself. – Alex Lowe Jun 28 '16 at 02:23
  • 1
    I see the problem. You renamed the same file in those two different commits, then deleted the "extra" one. I'll fork it and post an updated answer today if I get time. – Jeff Puckett Jun 28 '16 at 12:20
  • Thanks so much for your help. – Alex Lowe Jun 28 '16 at 15:29
  • I haven't forgotten about you, but in case you're still stuck in that conflict phase, you can get out of it with `git rebase --abort` or just start over and clone your project from Github to a new folder. – Jeff Puckett Jun 28 '16 at 16:23
  • If you could post an updated answer sooner rather than later that would be awesome. P.S: I am not trying to be rude at all. – Alex Lowe Jun 28 '16 at 18:45
  • For sure, sorry I had to goto work, am still here for a couple more hours. – Jeff Puckett Jun 28 '16 at 18:46
  • OK, sounds great. Sorry to bother you. – Alex Lowe Jun 28 '16 at 18:48
  • @Alex I updated it with the return of that lost file with my fork link. There's another new answer which is really good too you should take a look at. – Jeff Puckett Jun 29 '16 at 03:50
  • Could you please show me each of the commands that you had to do in order to achieve a linear repository? The only reason that I am asking is that I learn by doing and just doing it for me does not help me learn especially if I had to do this in the future. Thanks :-) – Alex Lowe Jun 29 '16 at 21:00
  • @Alex OK I had to step through the process again to take screenshots, so the commit hashes are all different again, but the full procedure is there now. – Jeff Puckett Jun 29 '16 at 22:03
2

With providing your source code, I was able to do the following steps to your current repo, with @jpaugh 's answer in mind, In order to get the exact same code as you began with. The reason you are having trouble with @jpaugh 's answer is because we have remove/remove merge conflicts that we have to manual resolve.

Because you already resolved this merge conflict, we are going to do exactly what you did to resolve it the first time.

Grab the branch you want to change the history of.

git checkout origin/master

We are going to rebase a commit that is before your merge. By default, rebase doesn't preserve merge history, so this will replay your work linearly from that commit to where your origin/master is.

git rebase 0fa9983

But we have some problems. The merge in history has a remove/remove conflict that you had resolved. Let's add the changes you made in the resolve commit8006e70 and the branch 051dae3 to mimic that you made them immediately following the other branch, not in parallel.

git checkout 8006e70 Backup\ scripts/backup\ ftp\ v.4\ \(final\).py
git rm Backup\ scripts/backup\ final\ ftp.py
git rm Python/Backup\ scripts/backup\ final\ ftp.py

So now we are going to commit our changes as a new commit, but let's use the C tag to give it the same commit message as the merged commit we are replacing. Use the hash of the branch we want the message from.

git commit -C 051dae3

Let's use the continue command, to continue rebasing.

git rebase --continue

We have a new problem, and that is we merged two commits into one (the merge and branch). It is still trying to be played out in continuing the rebase. If we had used the -i tag for our original rebase command, we could have indicated we should skip this commit, but this example is all command line. We are going to skip it here. It failed here because all the changes that it made have already happened. We chose to have the commit message in the branch, not the short renamed some files message, so your intentions are clear when you look at our new history. Go ahead and skip this conflict, allowing the rebase to continue through the rest of history.

git rebase --skip

We don't have any more rebasing left. The function has exited. Now we have a detached head that has a linear commit history. Let's check if we succeeded in recreating history, but keeping the files the same.

git diff origin/master

The last diff shows there are no changes to your files, just the history. This produces the following history:

$ git log --oneline --graph
* e8eec78 Removed a gitignore specifier from the .gitignore file
* 2d51522 Added 'Lecture 26 Files'
* 93f6908 Added 'Lecture 25 Tuples'
* 2b3d9c5 Added 'Lecture 23 Dictionaries'
* a8bcfaa Reorganized/Fixed all of the code/comments on all of the lectures that I have done up to this point
* f3d0448 Added 'Lecture 21 Lists'
* 7ce1af4 Added 'Lecture 19 Print Formatting'
* 740f459 Changed the numbering of the files
* da6f679 Added 'Lecture 16 Strings'
* 2d4abf6 Reorganized the repository
* 1d88453 Added 'Remove Numbers v.1.py'
* 85cda36 Added 'Lecture 14 Numbers'
* 3d89d1a Deleted an unneeded folder
* bc41dc6 Rename backup final ftp.py to backup ftp v.4 (final).py
* 52a1354 Renamed some of the files
* 0fa9983 Everything is in working order
* dfc9747 Fixed a few if statements
* e183900 Added Countdown Timers and other help to the portfolio
* 2377980 Removed some unnecessary lines of code
* 4172115 Added some new fetures to the script
* 4a7f2d1 Fixed some minor bugs in the code
* 589251c Removed text from a variable
* 93e9742 Finished the backup final ftp script for now
* c5687a3 Added the feature to remove files/folders that are older than a certain age
* 19a495f Removed some blank lines at the bottom of the file
* c2c58c3 Converts the folder to a .tar.bz2 and then deletes the folder
* 6718968 Made some minor changes to the code to increase usability
* b7d6608 Initial commit

Notice how the commit hashes have changed? This is because the history has changed. If you want to propagate this back to your origin/master, you need to force it. This is because pushes fail if the history hashes don't match, and we just changed the majority of our history hashes.

Do git push --force origin/master if you want to propagate those changes upstream.

Remember that other people could have forked your remote if it is public facing, and that it is best practise to not change git history if others have access to those commits.

$ git --version
git version 2.9.0
Bryce Drew
  • 5,777
  • 1
  • 15
  • 27
  • If possible could you please explain what each of those commands are doing? Just so for future reference. – Alex Lowe Jun 29 '16 at 04:09
1

The reason your history is like this is because you created a new branch at some point, then merged it back in. (Or someone else did.)

If you're using GitHub, then it could also mean you accepted a pull-request, which is tantamount to a merge. If so, there is no way around this, in general. From their point of view, this is a reasonable choice, as merging is more robust than rebasing, and preserves authorship changes better: we just have to live with uglier log output. In that case, you can stop reading here.


I don't know of an automated way to change this, but I can show you how to rebase your commits to the way you want. That involves rewriting the history from the point of the mistake all the way to the HEAD, creating different commits with the same content.

However, I noticed that the branch you're looking at is not a local branch, but the origin branch for your local copy (assuming usual Git convention here). I understand the desire for clean and pretty log output, however, there is a practical reason not to rebase old history:

Warning: Although it is possible to rebase your commits locally, it is not a good idea to push such changes to a public project. Rebasing your history always causes problems for others with whom you've shared the unmodified history. So, anyone else involved with your project will be hosed.


With that warning, here goes:

  1. First, make a copy of your repository, so that you have a backup. (A rebase command never loses data, but if you're unfamiliar with the rebase command, it can be hard to get back to a known state if you make a mistake or change your mind.)
  2. Check out a new branch based on origin/master, since you can't modify it directly (git checkout -b new-master origin/master)
  3. Next, do a git rebase --interactive <commitish> where <commitish> identifies the commit labeled "Everything is in working order" (0fa9).
  4. The rebase operation will now open up in your editor, for your approval.
  5. The first line will be the one called "Renamed some of the files" (09ab). Change its action from pick to edit.
  6. Save the file and close it. The rebase operation will begin.
  7. When the rebase stops after the first commit, git cherry-pick <commitish> where <commitish> describes the commit called "Rename backup file ftp.py to ...".
  8. git rebase --continue
  9. Verify the results. You'll notice that every commit has a different ID now, despite having the same description, and same content (diff). This is because you have written a new history with the old changes. The commit date will have changed, but not the authorship date.

Now, if you want to inflict others with your new history, you can do so by pushing these changes to your origin repository:

$ git push --force origin/master

The --force is required because this will overwrite the existing branch, and invalidate any branches made from that, including the local repository of all contributors.

jpaugh
  • 6,634
  • 4
  • 38
  • 90