How to squash commit on a single branch - github

I have a single branch named develop. I have around 20 commits in it. I want to squash all of them in to one commit, and rename it to "Initial commit".
So at the end, the branch will have one commit named "Initial commit".
I have tried:
git rebase -i HEAD~20
And I can pick or "f" the commits. That I understand, but what do I do after I save the changes?
All info about squashing assumes, I had another branch initially (master). I only have and will only have a develop branch.

Let us take this as the complete problem domain of the question:
I have a single branch named develop. I have around 20 commits in it. I want to squash all of them in to one commit, and rename it to "Initial commit". So at the end, the branch will have one commit named "Initial commit".
You are wishing that the most recent n commits were one commit. I call this the first type of regret. The first type of regret is cured by doing a soft reset and a commit. Here's a demonstration. First, I'll make a bunch of commits on a develop branch:
git init
echo "one" > file
git add .
git commit -m "one"
git branch develop
git checkout develop
echo "two" >> file
git add .
git commit -m "two"
echo "three" >> file
git add .
git commit -m "three"
echo "four" >> file
git add .
git commit -m "four"
echo "five" >> file
git add .
git commit -m "five"
git log --oneline
# 2febafe (HEAD -> develop) five
# 363ca9f four
# 429b266 three
# d46e6df two
# b8bf284 (master) one
cat file
# one
# two
# three
# four
# five
As you can see, I've got commits two, three, four, and five on develop. I wish those were just one commit. Here we go:
git reset --soft b8bf284
git add .
git commit -m "unified"
Okay, let's look around:
git log --oneline
# 408bdd1 (HEAD -> develop) unified
# b8bf284 (master) one
cat file
# one
# two
# three
# four
# five
Yup, there is now just one commit on develop after the point where it diverged from master, and everything I did (all five lines of the file) is in it.

Related

How can I git add, commit and push in one line with multiple specified file?

I want to combine the git push command to one line, such as gitpush -m "fix bug #123" -f file1.html, file2.html ...
only file1.html and file2.html will commit and update to the server
how to make it?
try adding the following function to your .bashrc (or .bash_profile if Mac):
function allinone() {
for i in ${#:2}
do
git add $i
done
git commit -a -m "$1"
git push
}
Then you just can add, commit and push typing:
allinone "Adding, commiting and pushing 3 files at once" file1.html file2.html file3.html
The first parameter must be the comment, and the rest of the parameters are the files that you want to add, commit and push (notice that files are separated by spaces)
I hope this helps!

What happens to original changesets after an Hg "history rewrite" (histedit, commit --amend), and how can they be recovered?

In Git - with it's immutable changeset objects and mutable refs - I know that the original commits remain, which gives me a warm fuzzy feeling after an 'oops' "history rewriting" moment.
For example, after a "history rewriting" git rebase the original changesets (cbe7698, 09c6268) are still there and a new changeset (08832c0) was added. I can easily restore/access the other changesets until such a time as they are pruned.
$ git log --oneline --graph --decorate $(git rev-list -g --all)
* 08832c0 (HEAD -> master) Added bar and quxx to foo.txt
| * cbe7698 Added quxx to foo.txt
| * 09c6268 Added bar to foo.txt
|/
* 895c9bb Added foo.txt
Likewise, even a git commit --amend preserves the original/amended commit (d58dabc) and adds a new changeset:
$ git log --oneline --graph --decorate $(git rev-list -g --all)
* d9bb795 (HEAD -> master) Added cats and dogs to pets.txt
| * d58dabc Added cats to pets.txt
|/
* 08832c0 Added pets.txt
However, what happens in Hg after a "history rewriting" operation?
Do the original commits cease to exist? And if they still do exist, how can they be accessed and/or recovered?
It depends on whether you have the evolve extension installed or not. If you have the evolve extension installed, the only command that will actually remove revisions from the repository is hg strip; other commands leave the original commits in place, but hide them. You can see them with hg log --hidden (or other commands with the --hidden flag). If you want to get rid of them permanently, hg strip --hidden -r 'extinct()' can be used [1].
Most people, however, will just be using base Mercurial. In this case (and for hg strip even with evolve), the removed changesets will be stored as bundles in .hg/strip-backup. A Mercurial bundle is basically a read-only overlay repository; you can use hg log -R, hg tip -R, hg incoming, hg pull, etc. on it. This is all you need in principle, but it can be a bit cumbersome typing out the paths.
For convenience, Facebook has published a number of experimental extensions for Mercurial. Among them, the backups extension provides a convenience command (hg backups) that basically lists the commits in each bundle in .hg/strip-backup (similar to what hg incoming with an appropriate template would do) and hg backups --recover to pull in the changesets from the stripped commits (similar to what hg pull would do).
[1] Note that even then, a backup will be stored in .hg/strip-backup. If you want to get rid of them really permanently, you will also have to remove that backup.

Why these commands in mercurial create a new head?

I was asked as an assignment to figure out which of the following lines causes the number of heads to change, now I checked and saw that lines 16 and 20 add new heads to their repository, but I'm not quite sure why.. I understand that the number is changed when there's a conflict but it's a bit unclear here..
Can anyone please help me understand? :)
Thanks.
1: /home/user> hg clone http://remoteserver/mainrepository first
2: /home/user> cd first
3: /home/user/first> echo one > a.txt # Create a new file a.txt containing “one”
4: /home/user/first> hg add a.txt
5: /home/user/first> hg commit -m "Added a file"
6: /home/user/first> cd ..
7: /home/user> hg clone first second
8: /home/user> cd second
9: /home/user/second> echo two >> a.txt # Append a line to a.txt containing “two”
10: /home/user/second> hg commit -m "Modified a file"
11: /home/user/second> hg push -f http://remoteserver/mainrepository
12: /home/user/second> cd ../first
13: /home/user/first> echo more > b.txt # Create a new file b.txt containing “more”
14: /home/user/first> hg add b.txt
15: /home/user/first> hg commit -m "Added another file"
16: /home/user/first> hg pull # default pulls from where repo was cloned
17: /home/user/first> echo ‘‘even more’’ > c.txt # Create a new file c.txt
18: /home/user/first> hg add c.txt
19: /home/user/first> hg commit -m "Added yet another file"
20: /home/user/first> hg push -f
Steps 1-5 leave the main and first repositories in the following state, assuming a r1-r3 was the original state of main:
Main: r1--r2--r3
First: r1--r2--r3--r4
Steps 6-11 copy first to second, commit something to second, and push it back to main:
Main: r1--r2--r3--r4--r5
First: r1--r2--r3--r4
Second: r1--r2--r3--r4--r5
Steps 12-15 go back to first and commit something again:
Main: r1--r2--r3--r4--r5
First: r1--r2--r3--r4--r6
Second: r1--r2--r3--r4--r5
Step 16 pulls from main into first, bringing in the missing r5, but note both r6 and r5 have the same parent of r4, so this creates a branch:
Main: r1--r2--r3--r4--r5
First: r1--r2--r3--r4--r6
\-----r5
Second: r1--r2--r3--r4--r5
Steps 17-19 create another commit on first:
Main: r1--r2--r3--r4--r5
First: r1--r2--r3--r4--r6------r7
\-----r5
Second: r1--r2--r3--r4--r5
Step 20 forces a push back to main, creating a branch there as well:
Main: r1--r2--r3--r4--r5
\-----r6--r7
First: r1--r2--r3--r4--r6------r7
\-----r5
Second: r1--r2--r3--r4--r5
Instead, a better practice is to merge locally in first so the main repository isn't left with two heads. So between steps 19 and 20 perform hg merge and then hg push without forcing with -f. The result would then be:
Main: r1--r2--r3--r4--r5----------m8
\-----r6--r7-/
First: r1--r2--r3--r4--r6------r7--m8
\-----r5-----/
Second: r1--r2--r3--r4--r5
Let's take these steps a bunch at a time and explain what happens:
Step 1-2: Clone a repository, and update the working folder to the tip, then enter that folder.
Step 3-5: Commit another changeset, creating a new tip (but not another head).
Step 6-8: Clone the first repository into a new one, update the working folder of this as well, then enter that folder.
Step 9-10: Commit another changeset on top of tip in the second clone.
Step 11: Push this new changeset into the original source. This repository, the one we cloned from to create first thus now has one changeset in addition to those we cloned in step 1.
Step 12: Go back to the first repository
Step 13-15: Commit another changeset above the current changeset in the first.
Step 16: Pull from the original source, here we bring in the changeset we first added in the second clone and then pushed in step 11 above. This will make our first repository now have two heads.
Step 17-19: Commit yet another changeset on top of the previous one, this does not create another head, we're just "extending" that head with another changeset.
Step 20: Pushes to the original source, forcing the push, which will push new changesets and create another head in the remote repository.
Now, why does this happen and is this OK?
First, OK? Yes, definitely. Well, sort of. Creating additional heads in a local repository is both OK and recommended. For instance, if you discover that you introduced a bug some time ago the recommended method for fixing it is first updating back to the changeset that introduced the bug, then fixing the bug in the working folder, and then committing the bugfix on top of the original changeset.
The "sort of" above comes from the fact that additional heads should be resolved locally before pushed. You should almost never push additional heads into remote repositories. As such, the -f parameter to hg push is best left alone.
The correct way to fix additional heads is to merge, this takes two parents and merges the changes introduced in those two parallel branches back into one head.
So, why does this happen? Well, the best way to learn that is just to read up on how distributed version control systems work.
In short, if you clone a repository down to your local machine, and then work locally, at the same time that other people work and push new changesets of their own up into the repository you cloned from, then eventually when you try to push you will be told this introduces additional heads, which is bad.
To resolve this you should pull, and merge your head with the head that was pulled down (that head will contain the new changesets others contributed in the meantime), before you reattempt your push.

Mercurial Subrepositories: Prevent accidental recursive commits and pushes

I work on a team where we have a code in a mercurial repository with several subrepositories:
main/
main/subrepo1/
main/subrepo1/subrepo2/
The default behavior of Mercurial is that when a hg commit is performed in "main", any outstanding changes in the subrepositories "subrepo1" and "subrepo2" will also be committed. Similarly, when "main" is pushed, any outgoing commits in "subrepo1" and "subrepo2" will also be pushed.
We find that people frequently inadvertently commit and push changes in their subrepositories (because they forgot they had made changes, and hg status by default does not show recursive changes). We also find that such global commits / pushes are almost always accidental in our team.
Mercurial 1.7 recently improved the situation with hg status -S and hg outgoing -S, which show changes in subrepositories; but still, this requires people to be paying attention.
Is there a way in Mercurial to make hg commit and hg push abort if there are changes/commits in subrepostories that would otherwise be committed/pushed?
Since Mercurial 1.8 there is a configuration setting that disables recursive commits. In the parent repositories .hg/hgrc you can add:
[ui]
commitsubrepos = no
If a commit in the parent repository finds uncommitted changes in a subrepository the whole commit is aborted, instead of silently committing the subrepositories.
Mercurial 2.0 automatically prevents you from committing subrepositories unless you manually specify the --subrepos (or, alternatively, -S) argument to commit.
For example, you try to perform a commit while there are pending changes in a subrepository, you get the following message:
# hg commit -m 'change main repo'
abort: uncommitted changes in subrepo hello
(use --subrepos for recursive commit)
You can successfully perform the commit, however, by adding --subrepos to the command:
# hg commit --subrepos -m 'commit subrepos'
committing subrepository hello
Some things to still be careful about: If you have changed the revision a subrepository is currently at, but not the contents of the subrepository, Mercurial will happily commit the version change without the --subrepos flag. Further, recursive pushes are still performed without warning.
One notion is to use URLs to which you have read-only access in your .hgsub files. Then when you do actually want to push in the subrepo you can just cd into it and do a hg push THE_READ_WRITE_URL.
One possible solution, using VonC's "pre-commit" idea.
Setup two scripts; the first check_subrepo_commit.sh:
#!/bin/bash
# If the environment variable "SUBREPO" is set, allow changes.
[ "x$SUBREPO" != "x" ] && exit 0
# Otherwise, ensure that subrepositories have not changed.
LOCAL_CHANGES=`hg status -a -m`
GLOBAL_CHANGES=`hg status -S -a -m`
if [ "x${LOCAL_CHANGES}" != "x$GLOBAL_CHANGES" ]; then
echo "Subrepository changes exist!"
exit 1
fi
exit 0
The second, check_subrepo_push.sh:
#!/bin/bash
# If the environment variable "SUBREPO" is set, allow changes.
[ "x$SUBREPO" != "x" ] && exit 0
# Otherwise, ensure that subrepositories have not changed.
LOCAL_CHANGES=`hg outgoing | grep '^changeset:'`
GLOBAL_CHANGES=`hg outgoing -S | grep '^changeset:'`
if [ "x${LOCAL_CHANGES}" != "x$GLOBAL_CHANGES" ]; then
echo "Global changes exist!"
exit 1
fi
exit 0
Add the following to your .hgrc:
[hooks]
pre-commit.subrepo = check_subrepo_commit.sh
pre-push.subrepo = check_subrepo_push.sh
By default, hg push and hg commit will abort if there are outstanding changes in subrepositories. Running a command like so:
SUBREPO=1 hg commit
will override the check, allowing you to perform the global commit/push if you really want to.
May be a pre-commit hook (not precommit) could do the hg status -S for you, and block the commit if it detects any changes?

How to edit incorrect commit message in Mercurial? [duplicate]

This question already has answers here:
Mercurial: how to amend the last commit?
(8 answers)
Closed 5 years ago.
I am currently using TortoiseHg (Mercurial) and accidentally committed an incorrect commit message. How do I go about editing this commit message in the repository?
Update: Mercurial has added --amend which should be the preferred option now.
You can rollback the last commit (but only the last one) with hg rollback and then reapply it.
Important: this permanently removes the latest commit (or pull). So if you've done a hg update that commit is no longer in your working directory then it's gone forever. So make a copy first.
Other than that, you cannot change the repository's history (including commit messages), because everything in there is check-summed. The only thing you could do is prune the history after a given changeset, and then recreate it accordingly.
None of this will work if you have already published your changes (unless you can get hold of all copies), and you also cannot "rewrite history" that include GPG-signed commits (by other people).
Well, I used to do this way:
Imagine, you have 500 commits, and your erroneous commit message is in r.498.
hg qimport -r 498:tip
hg qpop -a
joe .hg/patches/498.diff
(change the comment, after the mercurial header)
hg qpush -a
hg qdelete -r qbase:qtip
Good news: hg 2.2 just added git like --amend option.
and in tortoiseHg, you can use "Amend current revision" by select black arrow on the right of commit button
I know this is an old post and you marked the question as answered. I was looking for the same thing recently and I found the histedit extension very useful. The process is explained here:
http://knowledgestockpile.blogspot.com/2010/12/changing-commit-message-of-revision-in.html
Last operation was the commit in question
To change the commit message of the last commit when the last mercurial operation was a commit you can use
$ hg rollback
to roll back the last commit and re-commit it with the new message:
$ hg ci -m 'new message'
But be careful because the rollback command also rolls back following operations:
import
pull
push (with this repository as the destination)
unbundle
(see hg help rollback)
Thus, if you are not sure if the last mercurial command was a hg ci, don't use hg rollback.
Change any other commit message
You can use the mq extension, which is distributed with Mercurial, to change the commit message of any commit.
This approach is only useful when there aren't already cloned repositories in the public that contain the changeset you want to rename because doing so alters the changeset hash of it and all following changesets.
That means that you have to be able to remove all existing clones that include the changeset you want to rename, or else pushing/pulling between them wouldn't work.
To use the mq extension you have to explicitly enable it, e.g. under UNIX check your ~/.hgrc, which should contain following lines:
[extensions]
mq=
Say that you want to change revision X - first qimport imports revisions X and following. Now they are registered as a stack of applied patches. Popping (qpop) the complete stack except X makes X available for changes via qrefresh. After the commit message is changed you have to push all patches again (qpop) to re-apply them, i.e. to recreate the following revisions. The stack of patches isn't needed any, thus it can be removed via qfinish.
Following demo script shows all operations in action. In the example the commit message of third changeset is renamed.
# test.sh
cd $(dirname $0)
set -x -e -u
echo INFO: Delete old stuff
rm -rf .hg `seq 5`
echo INFO: Setup repository with 5 revisions
hg init
echo '[ui]' > .hg/hgrc
echo 'username=Joe User <juser#example.org>' >> .hg/hgrc
echo 'style = compact' >> .hg/hgrc
echo '[extensions]' >> .hg/hgrc
echo 'mq=' >> .hg/hgrc
for i in `seq 5`; do
touch $i && hg add $i && hg ci -m "changeset message $i" $i
done
hg log
echo INFO: Need to rename the commit message on the 3rd revision
echo INFO: Displays all patches
hg qseries
echo INFO: Import all revisions including the 3rd to the last one as patches
hg qimport -r $(hg identify -n -r 'children(2)'):tip
hg qseries
echo INFO: Pop patches
hg qpop -a
hg qseries
hg log
hg parent
hg commit --amend -m 'CHANGED MESSAGE'
hg log
echo INFO: Push all remaining patches
hg qpush -a
hg log
hg qseries
echo INFO: Remove all patches
hg qfinish -a
hg qseries && hg log && hg parent
Copy it to an empty directory an execute it e.g. via:
$ bash test.sh 2>&1 | tee log
The output should include the original changeset message:
+ hg log
[..]
2 53bc13f21b04 2011-08-31 17:26 +0200 juser
changeset message 3
And the rename operation the changed message:
+ hg log
[..]
2 3ff8a832d057 2011-08-31 17:26 +0200 juser
CHANGED MESSAGE
(Tested with Mercurial 4.5.2)
In TortoiseHg, right-click on the revision you want to modify. Choose Modify History->Import MQ. That will convert all the revisions up to and including the selected revision from Mercurial changesets into Mercurial Queue patches. Select the Patch you want to modify the message for, and it should automatically change the screen to the MQ editor. Edit the message which is in the middle of the screen, then click QRefresh. Finally, right click on the patch and choose Modify History->Finish Patch, which will convert it from a patch back into a change set.
Oh, this assumes that MQ is an active extension for TortoiseHG on this repository. If not, you should be able to click File->Settings, click Extensions, and click the mq checkbox. It should warn you that you have to close TortoiseHg before the extension is active, so close and reopen.
EDIT: As pointed out by users, don't use MQ, use commit --amend. This answer is mostly of historic interest now.
As others have mentioned the MQ extension is much more suited for this task, and you don't run the risk of destroying your work. To do this:
Enable the MQ extension, by adding something like this to your hgrc:
[extensions]
mq =
Update to the changeset you want to edit, typically tip:
hg up $rev
Import the current changeset into the queue:
hg qimport -r .
Refresh the patch, and edit the commit message:
hg qrefresh -e
Finish all applied patches (one, in this case) and store them as regular changesets:
hg qfinish -a
I'm not familiar with TortoiseHg, but the commands should be similar to those above. I also believe it's worth mentioning that editing history is risky; you should only do it if you're absolutely certain that the changeset hasn't been pushed to or pulled from anywhere else.
Rollback-and-reapply is realy simple solution, but it can help only with the last commit. Mercurial Queues is much more powerful thing (note that you need to enable Mercurial Queues Extension in order to use "hg q*" commands).
I did it this way. Firstly, don't push your changes or you are out of luck. Grab and install the collapse extension. Commit another dummy changeset. Then use collapse to combine the previous two changesets into one. It will prompt you for a new commit message, giving you the messages that you already have as a starting point. You have effectively changed your original commit message.
One hack i use if the revision i want to edit is not so old:
Let's say you're at rev 500 and you want to edit 497.
hg export -o rev497 497
hg export -o rev498 498
hg export -o rev499 499
hg export -o rev500 500
Edit rev497 file and change the message. (It's after first lines preceded by "#")
hg import rev497
hg import rev498
hg import rev499
hg import rev500
There is another approach with the MQ extension and the debug commands. This is a general way to modify history without losing data. Let me assume the same situation as Antonio.
// set current tip to rev 497
hg debugsetparents 497
hg debugrebuildstate
// hg add/remove if needed
hg commit
hg strip [-n] 498
A little gem in the discussion above - thanks to #Codest and #Kevin Pullin.
In TortoiseHg, there's a dropdown option adjacent to the commit button. Selecting "Amend current revision" brings back the comment and the list of files. SO useful.