How to solve a Mercurial case-folding collision with branches involved? - merge

Today I went to merge a branch back into an old branch and got told that I couldn't update to the original branch due to a case-folding collision.
My repo looks a bit like this:
default branch, revision 1: add most files.
default branch, revision 2: adds files xyz and XYZ. Presumably was done on Linux machine. This was back in 2008.
default branch revisions 3-35: various other changes, no problems
revision 36: new feature branch added
feature branch, revision 71: notice on Windows that Sourcetree is reporting that xyz is deleted, so I commit that, which becomes...
feature branch, revision 72: this is the change that explicitly removes BOTH xyz and XYZ
At this point, I wanted to merge the feature branch back into the default, but it will not let me update back to version 71, or any other version except revision 1 when xyz was not in the repo at all. Obviously every changeset from 2 through to 71 is 'polluted' with these 2 files that I could only ever check out as 1 on my Windows and Mac machines.
The usual suggested solution - https://www.mercurial-scm.org/wiki/FixingCaseCollisions - does not work: it says "hg status should show the troublesome file in state 'R' and all other files in state '!'", but when I follow the steps, it actually shows all other files in state 'M', which is obviously not what I want and implies some data loss would occur if I proceeded.
Additionally, I would suspect I can never perform the desired merge on Windows or MacOS, because I need revision 35 to be in a usable state before I can pull anything into it.
Is there anything I can do here to fix it? All my files are safe, and I am also willing to lose all data and revisions in the xyz files to get this repo working again, but I would really prefer not to lose the repo and the changelog entirely.

I couldn't find a proper fix for this, so I had to borrow a Linux box to get a case-sensitive filesystem. Then the fix was:
Copy the entire repository to a Linux machine with Mercurial installed.
Revert/update to the latest revision on the default branch that had the 2 files with clashing names in the working copy, and hg rename them to names that didn't clash. Reapply any other changes since then.
Update to the revision on the other branch, do the same thing.
Perform the merge as normal.
Copy the repo and working directory back to Windows.
Remove/rename/modify the 2 renamed files so that only 1 remains, with whatever name is desired.
Steps 4 and 5 could probably have been done in either order.

I haven't done this in a while, but I've used the convert extension in the past to clean up case folding on Windows.
Note: I'm making the assumption that this is an essentially private repository to which you have exclusive access. If that is the case then this approach would work. (Even if that is not the case, this would still work technically but you'd have to publish it to other people as a new repo since the conversion described below would generate all new changeset IDs.)
You can do this using the convert extension to Mercurial, and the filemap option.
Convert can ... rename files during conversion, when you
supply it a mapping via the --filemap option.
The filemap is a file that specifies which files are to be included,
renamed, or omitted.
Each line can contain one of the following directives:
...
rename from/file to/file
...
The rename directive renames a file or directory. To rename from a
subdirectory into the root of the repository, use . as the path to
rename to.
The command line to do this would be something like:
hg convert --filehmap filemap.txt path\to\source\repo path\to\converted\repo
Where filemap.txt has to include something like:
rename path\to\XYZ path\to\xyz_which_was_uppercase
The conversion process should rename ALL "instances" of XYZ on all branches, so the fact that branches are involved I think becomes irrelevant.
Documentation.

As noted by the OP, the primary resource for guidance on managing case conflicts of the type described is https://www.mercurial-scm.org/wiki/FixingCaseCollisions
The directions given there are perhaps not as clear as they could be, so here is an annotated transcript based on one of the procedures given there.
We start with a repository named "repo", which has two files: a.txt and A.txt. On a remote machine:
REMOTE_MACHINE $ hg files
A.txt
a.txt
Now let's assume that repo has somehow been cloned onto the local machine (the computer on which the case-folding problem occurs). This might have been done using a command such as:
$ hg clone --noupdate "ssh://REMOTE/DIRECTORY/repo" # illustrative
If you don't want to alter repo on the local machine, then you could (for example) clone it, as by:
$ hg clone --noupdate repo xrepo # illustrative and optional
Now cd into the directory of the repository to be modified, e.g.
$ cd repo # or perhaps: cd xrepo
You might want to verify the integrity of the repository:
$ hg verify
Next come the two lines of black magic:
$ hg debugsetparents tip
$ hg debugrebuildstate
At this point, Mercurial will think you have the tip checked out,
and that all the files are missing (status '!'). In our case:
$ hg st -dram
! A.txt
! a.txt
Now we will run some ordinary hg commands to address the case-folding problem. Let's begin by extracting the files of interest manually:
$ hg cat a.txt > lc.a.txt
$ hg cat A.txt > uc.A.txt
Now remove the two problematic files from the repo:
$ hg -v remove --after a.txt A.txt
removing A.txt
removing a.txt
(The --after option above in effect instructs Mercurial to handle the fact that the files being removed (from the repository) are not on disk.)
Now we're back to normal. We might want to check in our renamed files:
$ hg -v add lc.a.txt uc.A.txt
adding lc.a.txt
adding uc.A.txt
$ hg st -dram
A lc.a.txt
A uc.A.txt
R A.txt
R a.txt
$ hg commit --message "renamed a.txt to lc.a.txt and A.txt to uc.A.txt"
$ hg files
lc.a.txt
uc.A.txt
$ cat lc.a.txt
lowercase a
$ cat uc.A.txt
uppercase A
$
# Verify that the original files are still there:
$ hg files --rev 0
A.txt
a.txt
At this point, to checkout all the other files, you could run: hg revert --all

Related

Mercurial: message "abort: index 00changelog.i is corrupted!" after running a sed command. How to deal with it?

I have a project versioned by Mercurial. Once, I ran a command such as...
$ find . -type f | xargs sed -i.bkp 's/my_func/another_func/'
...in this project. Then Mercurial just stopped to work on it:
$ hg status
abort: index 00changelog.i is corrupted!
The file 00changelog.i did not contain the replaced string, and even if I move 00changelog.i.bkp to 00changelog.i the problem persisted. hg verify didn't help either:
$ hg verify
abort: index 00changelog.i is corrupted!
I solved this problem by cloning the project from my remote repository in another directory and then copying .hg from my cloned repository to the corrupted one. However, I wonder: is there another more practical way of solving it? BTW, why does this problem happens if the "corrupted" file is not even altered?
Note that there are two 00changelog.i files:
one in .hg/00changelog.i which is a small backwards-compatibility placeholder for ancient versions
another (the one you damaged) in .hg/store/00changelog.i
I'd use convert to re-generate the repo or transplant to move the patches to the new one. Good luck.

Mercurial: Getting changed files from revision?

I can't get my head around how I can get only the files that were changed in my last revision, and not the complete repo, which i get by using Clone. This would be super helpful to for instance, deploy the last changed files.
What do I do?
Thanks
You cannot transfer just some files -- you must always have a full clone locally. So I suggest you make that once and then pull in changes incrementally.
You should then use hg status to show the names of files changes been revisions. You normally use hg status to see which files are changes compared to the working copy parent revision, but you can pass --rev to status to see changes between any two revisions!
So use
$ hg status --change tip
to see which files were changed in the tip changeset. Use
$ hg pull
$ hg status --rev .:tip
to see what files will be changed when you do a hg update after a pull. The update will take you from the current working directory parent, denoted by ., to the tip, assuming you are on the same named branch as tip. Otherwise hg update will take you to the tip-most changeset on your current branch.
You can use a template to hg log or hg tip to get all filenames:
hg tip --template '{files}'
or to get e.g. all files changed/added/modified by changeset 5:
hg log -r5 --template '{files}'
You can read more about templates Mercurial: The Definitive Guide, Chapter 11. Customizing the output of Mercurial or short help with hg help templating`
If you want to do something to the files e.g grep through them:
hg tip --template '{files}' | xargs grep foo
Careful this will break with filenames containing spaces!
To process all kinds of filenames something more complicated is needed (thanks to mg on #mercurial for the tip):
We need multiline styles for this suppose a file named zerosep contains:
changeset = "{files}"
file = "{file}\0"
Then you can use this to produce a \0 separated list of filenames and process it with xargs -0:
hg tip --style zerosep | xargs -0 grep foo
Information how this works can be found in: Listing files on multiple lines
Martin has the right way to get a list of what files have changed in a revision, and I've upvoted his answer, but just to expand on why your question is a little off:
In Mercurial every location has a full copy with all changes. If you're going to use Mercurial to deploy to your server then you have to clone everything to your server -- once. After that push/pull will move over only the (compressed) changesets that the server doesn't already have.
If you don't want to have everything that ever was on the server, then you need to use something other than mercurial for your deployment, perhaps something fed from the status commands Martin showed or a snapshot created from hg archive.
Personally, I'm okay with having full history on my servers, and just do a hg pull ; hg update on the server for each new deployment. It's tidy and efficient.

Retrieve old version of a file without changing working copy parent

How do you get a copy of an earlier revision of a file in Mercurial without making that the new default working copy of the file in your workspace?
I've found the hg revert command and I think it does what I want but I'm not sure.
I need to get a copy of an earlier revision of my code to work with for a few minutes. But I don't want to disturb the current version which is working fine.
So I was going to do this:
hg revert -r 10 myfile.pls
Is there a way to output it to a different directory so my current working version of the file is not disturbed? Something like:
hg revert -r 10 myfile.pls > c:\temp\dump\myfile_revision10.pls
The cat command can be used to retrieve any revision of a file:
$ hg cat -r 10 myfile.pls
You can redirect the output to another file with
$ hg cat -r 10 myfile.pls > old.pls
or by using the --output flag. If you need to do this for several files, then take a look at the archive command, which can do this for an entire project, e.g.,
$ hg archive -r 10 ../revision-10
This creates the folder revision-10 which contains a snapshot of your repository as it looked in revision 10.
However, most of the time you should just use the update command to checkout an earlier revision. Update is the command you use to bring the working copy up to date after pulling in new changes, but the command can also be used to make your working copy outdated if needed. So
$ hg update -r 10 # go back
(look at your files, test, etc...)
$ hg update # go back to the tip
The command you use is this:
hg cat -r 10 myfile.pls > C:\temp\dump\myfile_revision10.pls
Knowing a bit of Unix helps with Mercurial commands. Perhaps cat should have a built in alias print or something similar.

Committing to a different branch with commit -r

Does CVS allow committing a file to a different branch than the one it was checked out from? The man page and some sites suggest that we can do a cvs ci -r branch-1 file.c but it gives the following error:
cvs commit: Up-to-date check failed for `file.c'
cvs [commit aborted]: correct above errors first!
I did a cvs diff -r branch-1 file.c to make sure that contents of file.c in my BASE and branch-1 are indeed the same.
I know that we can manually check out using cvs co -r branch-1, merge the main branch to it (and fix any merge issues) and then do a check in. The problem is that there are a number of branches and I would like to automate things using a script. This thread seems to suggest that -r has been removed. Can someone confirm that?
If ci -r is not supported, I am thinking of doing something like:
Make sure the branch versions and base version are the same with a cvs diff
Check in to the current branch
Keep a copy of the file in a temp file
For each branch:
Check out from branch with -r
replace the file with the temp file
Check in (it'll go the branch as -r is sticky)
Delete the temp file
The replacing part sounds like cheating to me - can you think of any potential issues that might occur? Anything I should be careful about? Is there any other way to automate this process?
Note that a file may not be up-to-date even if diff shows zero output. For example, if you add a line of text to a file in one commit and remove it in the next you have zero difference along the path of two revisions.
As for the commit -r -issue. To me it seems like an experimental feature, and actually one you are better off by just using:
cvs update -r <branch> <file>
cvs update -j <ver> -j <ver> <file>
cvs commit <file>
Besides, propagating a single commit to all other branches programatically like the way you suggested is slightly questionable business since you usually need a quite a bit of human brain to resolve the conflicts.

Mercurial (hg) commit only certain files

I'm trying to commit only certain files with Mercurial.
Because of of hg having auto-add whenever I try to commit a change it wants to commit all files. But I don't want that because certain files are not "ready" yet.
There is
hg commit -I thefile.foo
but this is only for one file. The better way for me would be if I can turn off auto-add as in Git. Is this possible?
You can specify the files on the command line, as tonfa writes:
$ hg commit foo.c foo.h dir/
That just works and that's what I do all the time. You can also use the --include flag that you've found, and you can use it several times like this:
$ hg commit -I foo.c -I "**/*.h"
You can even use a fileset to select the files you want to commit:
$ hg commit "set:size(1k - 1MB) and not binary()"
There is no setting that will turn off the auto-add behavior and make Mercurial work like Git does. However, the mq extension might be of interest. That's an advanced extension, but it allows you do to
$ hg qnew feature-x # create new patch
$ hg qrefresh -s foo.c # add a file to the current patch
$ hg qrefresh -s bar.c # add another file to the patch
$ hg qfinish -a # convert applied patches to normal changesets
I don't really use MQ for this purpose myself, though, since I think it's enough to just specify the filenames on the command line.
If you want to commit a few files, and exclude many others, explicitly list the files you want to commit. -I is only needed if you want to use patterns instead of files.
If you want to commit many files, and exclude only a few files, using -X is more convenient.
E.g. given a repository containing "file_1", "file_2" and "file_3", the following are equivalent, but the latter is easier / faster to type:
hg commit file_1 file_2
hg commit -X file_3
As of 3.8 you can also use hg commit --interactive to select the files (before 3.8 you can use crecord extension for similar functionality). The --interactive (or just -i) flag will cause hg to prompt you for the files you want to include in the commit. As an added bonus you can even include/exclude chunks within the files.
Here is an example of what the interface looks like. Note this is an old screenshot (of crecord actually) so interface has changed slightly (but basics are the same).
Note to get that interface you need the curses interface enabled. You can do that for single run with hg commit --config ui.interface=curses --interactive or generally by adding the the following to your .hgrc:
[ui]
interface = curses