21

A developer recently left who left a ton of commits in a repo from a few months ago that are just like 'updated'. Ideally, I'd like to squash them into a single commit but I have only done this for recently commits.

How would I do it for like the following commits (assuming the from 2 months ago means that there are hundreds of these)?

.... from 2 months ago

aabbcc updated
aabbdd updated
aabbee updated
aabbff updated

Not wanting / needing anything fancy, just a simple solution. These commits haven't been publicly shared (other than with me today) so no issue of upsetting other people's commit history.

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
timpone
  • 19,235
  • 36
  • 121
  • 211

3 Answers3

9

In order to do a git squash follow those steps:

// X is the number of commits you wish to squash
git rebase -i HEAD~X

Once you squash your commits - choose the s for squash = it will combine all the commits into a single commit.

enter image description here


You also have the --root flag in case you need it

try: git rebase -i --root

--root

Rebase all commits reachable from <branch>, instead of limiting them with
an <upstream>.

This allows you to rebase the root commit(s) on a branch.  
When used with --onto, it will skip changes already contained in `<newbase>`   
(instead of `<upstream>`) whereas without --onto it will operate on every 
change. When used together with both --onto and --preserve-merges, all root 
commits will be rewritten to have `<newbase>` as parent instead.`
CodeWizard
  • 128,036
  • 21
  • 144
  • 167
  • 1
    I haven't - is there any way to pass rebase -i a range of commit ids like aabbcc..aabbbff – timpone Jan 12 '16 at 00:08
  • @timpone No. Interactive rebase is a script that uses `git cherry-pick` under the hood. With `cherry-pick` you can pick a range. – Kaz Jan 12 '16 at 00:08
  • Nope, you cannot use range – CodeWizard Jan 12 '16 at 00:09
  • 1
    shoot - a dumb ? but would it be possible to checkout at a specific point and then do something like `git rebase -i HEAD~200`? or would it be safer to just let this be which I'm totally fine with – timpone Jan 12 '16 at 00:17
  • Read this (and feel free to vote for it as well if you like it) http://stackoverflow.com/questions/34519665/how-to-move-head-checkout-revet-reflog-reset/34519716#34519716 – CodeWizard Jan 12 '16 at 00:22
  • Warning: if you run `git rebase -i --root` on a branch with tonnes of commits, expect a decent wait – scrowler Jan 12 '16 at 00:26
  • thx @codeWizard - that's my question so I can't vote for it - (was actually the start of hearing about this problem a couple of weeks ago). – timpone Jan 12 '16 at 02:45
  • 1
    Cool. let me know if you need any more help regrading this issue :-) – CodeWizard Jan 12 '16 at 05:26
  • @CodeWizard, Is there a way to squash a range of commits in middle of the branch commits? – Jiss Raphel Aug 12 '18 at 14:19
  • @CodeWizard I missed in this answer the process of jumping to and back from an old commit because newer commits exist. I did `git checkout `endCommit` before rebase and `git checkout master`afterwards. However, I don't see any effect... – David Apr 24 '19 at 07:47
5

I know that this is already an ancient question but I needed a solution for this.

Long story short, my local git repo (on NFS, no upstream) works as a back up to certain files and I wanted it to have a maximum of 50 commits. Since there are many files and the backups are taken rather often, I needed something that automatically squashes the history, so I created a script that both backs the files up and squashes the history.

#!/bin/bash

# Max number of commits preserved
MAX_COMMITS=50

# First commit (HEAD~<number>) to be squashed
FIRST_SQUASH=$(echo "${MAX_COMMITS}-1"|bc)

# Number of commits until squash
SQUASH_LIMIT=60

# Date and time for commit message
DATE=$(date +'%F %R')

# Number of current commits
CURRENT_COMMITS=$(git log --oneline|wc -l)

if [ "${CURRENT_COMMITS}" -gt "${SQUASH_LIMIT}" ]; then

    # Checkout a new branch 'temp' with the first commit to be squashed
    git checkout -b temp HEAD~${FIRST_SQUASH}

    # Reset (soft) to the very first commit in history
    git reset $(git rev-list --max-parents=0 --abbrev-commit HEAD)

    # Add and commit (--amend) all the files
    git add -A
    git commit --amend -m "Automatic squash on ${DATE}"

    # Cherry pick all the non-squashed commits from 'master'
    git cherry-pick master~${FIRST_SQUASH}..master

    # Delete the 'master' branch and rename the 'temp' to 'master'
    git branch -D master
    git branch -m master

fi

So, what the script basically does is (I removed the back up part):

  1. If there are more than 60 commits, it squashes all the commits from 50 to 60+ into a one commit.
  2. It creates and checks out a new branch based on the commit
  3. Cherry picks the remaining commits from the master (#1 to #49) to the branch
  4. Removes the master branch
  5. Renames the new branch to master.
jxcr0w
  • 111
  • 2
  • 3
  • this should be good choice for tracking binary files, but i got twice the size in .git folder with this approach. trying stuff here https://stackoverflow.com/questions/3797907/how-to-remove-unused-objects-from-a-git-repository wont get me significant smaller .git size. Is it because it uses cherry pick and remove branch instead of squashing? – izzulmakin Dec 24 '20 at 04:38
  • 1
    it's getting bigger because it uses `git add -A`, and i manually copied .git into .gitbk and that folder which obviously not in .gitignore, got added as well! in my case i edited that line into `git add \`git ls-tree --full-tree -r --name-only HEAD\`` and added `git gc --aggressive` – izzulmakin Dec 24 '20 at 05:57
  • 2
    `bc` isn't always present. You can use built-in bash arithmetic to add a bit more portability: `FIRST_SQUASH=$(expr $MAX_COMMITS - 1)` – EdwardTeach Dec 25 '20 at 22:25
4

The age of the commits doesn't matter, squashing commits is squashing commits.

If rebasing isn't preferable for you, or there are literally thousands of commits that you want to squash and can't be bothered with it, you could just reset softly to the first commit hash and re-commit everything:

$ git reset aabbff 
$ git commit -m "This commit now contains everything from the tip until aabbff"

You would then only have one commit, the same as rebase -> squash.

Community
  • 1
  • 1
scrowler
  • 24,273
  • 9
  • 60
  • 92
  • haha, it's not thousands but like 200 and they are from a period where he just everything a commit. Is there a way to reset from aabbff to another specific commit (like the reverse of the tilde character I think). Sorry, I'm definitely not a git guru so my biggest fear is to muck things up. – timpone Jan 12 '16 at 00:16
  • Hey @timpone - no, the `reset` command will reset from the tip of the branch back to the revision/hash you specify. If you want to target a range of commits somewhere in the middle of the branch history, you should rebase. – scrowler Jan 12 '16 at 00:24
  • Just to add additional explanation, the commit hash should be hash of the one commit **before** the commit from which you want to squash the changes – Aleks Aug 24 '21 at 12:26