Amending a commit with the effects of hg cp --after - mercurial

I have a file before.txt, which I want to split into two smaller files. What I should have done was
$ hg cp before.txt part1.txt # Then edit part1.txt
$ hg mv before.txt part2.txt # Then edit part2.txt
$ hg commit
Then, both part1.txt and part2.txt will have before.txt as part of their history, so the diff will show as deleting parts of a larger file, rather than deleting one file and creating a new one with some of its contents.
However, what I actually did was
$ cp before.txt part1.txt # Then edit part1.txt
$ hg mv before.txt part2.txt # Then edit part2.txt
$ hg commit
So before.txt is only present in the history of one of my two files. If I hadn't run hg commit, it seems clear to me that I could solve my problem with
$ hg cp --after before.txt part1.txt
or something similar to that. And I haven't pushed this commit upstream, so I should be able to edit it however I like. But I can't figure out how to do it. When I run that hg cp, I see:
$ hg cp --after before.txt part1.txt
before.txt: No such file or directory
before.txt: No such file or directory
abort: no files to copy
This makes sense: it can't record that edit as part of a new commit, because the source file doesn't exist. I want to record it as part of the previous commit, but I don't know how to do that except by recording it as part of a new commit and then amending it into the previous commit.

Here's one way to fix that situation:
$ hg shelve # get any miscellaneous, unrelated changes out of the way
$ hg up <parent of revision with the mistake in it>
$ hg cp before.txt part1.txt
$ hg mv before.txt part2.txt
$ hg revert -r <revision with the mistake in it> --all
$ hg commit
$ hg strip <revision with the mistake in it>
(I didn't actually try all these commands, hopefully no typos!)
The first step is optional depending on the state of your working directories.
Now part1.txt and part2.txt should have the correct contents. The use of revert was just to save having to manually re-edit the file changes. But you could also just redo it manually if that seems easier.
The use of revert to pull into the working folder the effects of another changeset is a trick I use a lot. Its like a manual way of amending which gives you total flexibility. But it only works well when the revision you are reverting your working copy to is closely related to the revision which is the parent of the working copy. Otherwise it will create numerous nuisance changes.

based on #DaveInCaz answer, here is a MCVE
mkdir tmpdir
cd tmpdir
hg init
echo line1 > before.txt
echo line2 >> before.txt
hg add before.txt
hg commit -m "my before"
cp before.txt part1.txt
hg add part1.txt
hg mv before.txt part2.txt
echo line1 > part1.txt
echo line2 > part2.txt
hg commit -m "my bad"
hg shelve
hg up -r -2
hg cp before.txt part1.txt
hg cp before.txt part2.txt
hg revert -r -1 --all
hg commit -m "my good"
hg strip -r -2
some remarks:
twice hg cp, because the later revert will delete the file before.txt for us, otherwise it would complain about missing it
revert --all at least in my version it requires to specify what is being reverted
the hg shelve is to be safe, before switching over to a different revision
the hg up -r -2 could have -C since previous shelve made us safe, that way you can retry final steps with different approaches and see what suits you better

Related

Mercurial: move MQ patch to shelve?

Emacs VC mode show changes in directory state but ignore MQ changes...
I want to remove patch from queue and apply it on working directory.
This is possible with usual patch command, but it require passing some arguments and paths to utilities (which is inconvenient as not all environment allow autocompletion).
I expect hg built-in solution. Ideally - to move patch to shelve.
Moving changes from MQ to working tree and forgetting it:
$ hg qgoto my.patch
$ hg qpop
$ hg qdel --keep my.patch
$ patch -p1 .hg/patches/my.patch
$ rm .hg/patches/my.patch
In Emacs to see difference in top MQ patches I find command C-u C-x v D qparent RET qtip RET which is:
$ hg diff -r qparent:qtip
or shorter:
$ hg diff -r qparent
or:
$ hg qdiff
but latest command doesn't present in Emacs VC mode.

Hg update to previous commit removing any changes

working directory
Did a
hg commit (resulted in rev3)
Copied some directories inside ... changed some files. Did
hg add
hg commit (resulted in rev4 - tip)
How to get back exactly to the state of rev3. hg update 3 will change the files to the state they were in rev3, but it will also leave all the copied directories inside. I want to get the working directory without the copied directories and files, which were added after rev3.
That doesn't happen for me:
> hg init
> mkdir first
> jed first/foo.txt
> hg add
adding first/foo.txt
> hg commit -m asd
> mkdir second
> jed second/foo.txt
> hg add
adding second/foo.txt
> hg commit -m asd
> hg update 0
Now first exists, but second doesn't...
Are you sure you added everything in the new directories before your commit?
Like Jon writes, Mercurial will normally try to clean up after itself. So when you hg update you should get back exactly what you committed. What I guess you're seeing is the following behavior:
$ hg init repo
$ cd repo
$ echo "# some C program" > foo.c
$ hg add foo.c
$ hg commit -m first
$ mkdir dir
$ echo "$ other C program" > dir/bar.c
$ hg add dir/bar.c
$ hg commit -m second
$ echo "object file" > dir/bar.o
$ hg update 0
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ ls
foo.c dir
$ ls dir
bar.o
So the untracked dir/bar.o file has been left behind, but the tracked dir/bar.c file has been correctly removed. That is: Mercurial wont delete untracked files since it doesn't know if they contain valuable data. This applies even if the files are ignored by a pattern in .hgignore. If the directory had only contained the tracked dir/bar.c file, then the directory would have been completely removed when you update to a revision where it isn't needed.
The normal way to clean up untracked files is to use the purge extension.

Mercurial qfold ALL patches?

Turns out there is no hg qfold -a.
I tried hg qfold $(hg qunapp) and hg qunapp | xargs hg qfold but couldn't get it to work. Any ideas?
With your xargs approach, did you remember that qfold only folds unapplied patches into an applied patch? This worked for me (Windows) to fold all patches into the first patch:
hg qpop -a # remove all patches
hg qpush # apply first one
for /f %i in ('hg qunapplied') do hg qfold %i # fold remaining patches
Hmm... we could add a -a flag... But until we do, I would use the histedit or collapse extensions or maybe just do it myself:
$ hg update qparent
$ hg revert --all --rev qtip
$ hg commit -m 'Everything in one commit'
$ hg qpop -a
You then need to remove the patches -- perhaps you can just remove .hg/patches entirely, or you can delete some of them and edit .hg/patches/series to match.
hg qunapp | xargs -I'{}' hg qfold '{}'

In mercurial, how do I apply a reverse-patch to a particular file?

Related to Mercurial: Merging one file between branches in one repo , I'm trying to perform a backout operation on a single file, even though that file was one of many participants in the revision being backed out.
HG being the changeset-oriented tool that it is, it doesn't want to operate on files.
Closest I could find was to use hg export to create a diff, hand-edit the diff, and then hg import to patch the file in reverse order.
..but then I hit this annoying situation where http://hgbook.red-bean.com/read/finding-and-fixing-mistakes.html claims that there is a --reverse option to hg patch when there is not.
So the closest thing I can think of is to generate a hand-edited patch as above, and then using vanilla patch -R to apply a reverse patch.
The hg backout command would seem to be useful here, but is actually a red herring.
There has GOT to be a better way, no?
You can do it using just the -I (include names matching the given patterns) argument for backout with a single line:
hg backout --merge -I thefiletorevert -m 'message' OFFENDINGREVISIONID
Example Script:
hg init testrepo
cd testrepo
echo -e "line1\n\nline3" > file1
echo -e "line1\n\nline3" > file2
hg commit -A -m 'changes to two files'
perl -pi -e 's/line1/line 1/' file1
perl -pi -e 's/line1/line 1/' file2
hg commit -m 'put spaces in line1'
perl -pi -e 's/line3/line 3/' file1
perl -pi -e 's/line3/line 3/' file2
hg commit -m 'put spaces in line3'
hg backout --merge -I file1 -m 'remove spaces from line1' 1
Sample output:
adding file1
adding file2
reverting file1
created new head
changeset 3:6d354f1ad4c5 backs out changeset 1:906bbeaca6a3
merging with changeset 3:6d354f1ad4c5
merging file1
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
Resulting File Contents:
file1:line1
file1:line 3
file2:line 1
file2:line 3
notice that file1 is missing it's space in line one after the backout of the middle changeset, and the verbose log shows only one file changed in the backout:
$ hg log -v -r tip
changeset: 3:6d354f1ad4c5
tag: tip
parent: 1:906bbeaca6a3
user: Ry4an Brase <ry4an#mini>
date: Mon Sep 14 12:17:23 2009 -0500
files: file1
description:
remove spaces from line1
Here's what I would do: Use a fresh clone of the tip revision.
hg backout --merge -r revision_where_the_change_happened
to merge the reversed changes into the working copy.
Now copy the file in question to your regular working copy and commit
hg commit -m "Reversed the changes to file.h made in revision bla"
and throw away the clone you created above.
This way, mercurial doesn't know that there is a connection between revision_where_the_change_happened and this commit. If you want mercurial to remember this, instead do a
hg revert {all files except the one in question}
after merging the backout commit into the working copy and before commiting. For the second way, you don't need to work on a clone, because you want to keep the backout commit.
I would guess that the choice of which way you use depends on how big a part of the changeset the particular file change was.
Use the revert command.
hg revert -r1 file
This should revert the contents of file to the version in revision 1.
You can then further edit it and commit as normal.

logging mercurial transactions

this is a small addition to the previous script, and this time I would like to log details for the backup.
script /tmp/commit-push-log
# add all files to the repository
for REPOSITORY in $#
do
cd $REPOSITORY
# commit the changes
hg commit -A -m "Commit changes `date`"
# push the changes to the remote repository
if hg push
then
logger hg push completed without failure
else
logger hg push fails
fi
done
exit
cat /tmp/commit-push-log | logger
rm /tmp/commit-push-log
the problem is that i don't see any mercurial messages in the log. What can go wrong in my script?
You should not use static tmp filenames. Use mktemp, it's far safer.
You should cd "$REPOSITORY" instead of "cd $REPOSITORY" or things will get funny when REPOSITORY will contain any spaces or special characters.
You should not write automated commit comments. See here for the great article on this topic.
hg probably outputs errors to stderr. Use hg commit -A -m "$comment" 2>&1 and hg push 2>&1
my current version
for REPOSITORY in $#
do
# new temp file
OUTPUT_LOG=`tempfile`
echo -n > $OUTPUT_LOG
# checks whether $REPO is a repo
if [ ! -d $REPOSITORY/.hg ]; then
echo "Not a repository: $REPOSITORY"
exit 1;
fi
# change to that dir
cd "$REPOSITORY"
logger "Repository: $REPOSITORY"
# commit the changes
hg commit -A -m "Commit changes `date`" 2>&1 >> $OUTPUT_LOG
# push the changes to the remote repository
if hg push 2>&1 >> $OUTPUT_LOG
then
logger hg push completed without failure
else
logger hg push fails
exit 1;
fi
# log the contents and delete the tempfile
cat $OUTPUT_LOG | logger
rm -f $OUTLOG_LOG
done
exit 0