hg: replacing directory contents in one change - mercurial

With hg, how can I replace the contents of a directory in one change?
More specifically, I have headers for a large C++ library checked into my repo and I'd like to update them (including adding and removing files) to the latest version of the library. And I'd like to do this in one change, rather than first a removal of the old version and then addition of the new, which would break tests and produce unhelpful diffs.

Within your mercurial repo:
# Change to parent directory of your vendor directory
cd vendor_dir/..
rm -rf vendor_dir
# unpack/copy/checkout new vendor sources into a new vendor_dir
# Tell mercurial to figure out what changed
# Feel free to play with the similarity percentage and the (-n) do nothing switch
hg addremove --similarity 70 vendor_dir
hg commit vendor_dir
Updated:
Changed to work on the parent directory since hg rm * within vendor_dir misses dot files, if any.
Change from hg rm to rm -rf because I wrongly assumed that addremove after rm would do the right thing. Hint: it doesn't.
Realized that the default similarity of 100% is not appropriate for vendor releases.

If they are all changed then, (assuming a flat directory structure:
hg remove \path\to\directory\to\replace\*.h
copy \path\to\new\files\*.* \path\to\directory\to\replace\
hg add \path\to\directory\to\replace\*.*
hg commit -m "Library SoAndSo headers replaced"
hg push
The first line says forget all the files on the next commit but then the new ones are added in the same commit - remember only the last but one line actually changes the local copy of the repository and it only becomes public on the last line.
If you do have sub-directories then you can just remove the *.h on the first line, use xcopy or explorer to copy the new directory structure into place and remove the *.* on the 3rd line.

Related

Why don't mercurial file sets work when adding files?

I'm trying to use mercurial file sets to add all the files in a directory tree, excluding very large files and any binary files. Cribbing from the mercurial documentation, this command should do it:
hg init
hg add 'set: size("<1M") and not binary()'
However this returns a status code of 0, and hasn't added anything to my new, empty repo. I've tried just 'set: not binary()' and that didn't work either.
The frustrating thing is that although I can google for mercurial file sets, and find lots of examples, I can't find anything to help troubleshoot when it doesn't work!
I don't have a .hgignore file, and it's a fresh empty repo. Mercurial 4.2.2.
The directory where I'm testing this has a couple of artificially created files for the purpose of testing. In my real use case, I inherit a multi-gigbyte tarball of assorted sources and binaries from a client, and I want to get all the sources into mercurial before I start hacking to fix their problems, hence the need to exclude the binaries and large files that otherwise choke mercurial.
Here's my little test script:
#!/bin/sh -ex
dd if=/dev/urandom of=binary_1k bs=1 count=1024
dd if=/dev/urandom of=binary_2M bs=1 count=2097152
echo "This. Is, a SMALL text file." > text_small
hexdump binary_1k > text_1k
hexdump binary_2M > text_2M
ls -lh
file binary_1k
file binary_2M
file text_1k
file text_2M
hg init
hg add 'set: size("<1M") and not binary()'
hg status -a
hg add 'set: not binary()'
hg status -a
hg add 'set: size("<1M")'
hg status -a
At the end of this, each status command reports no files in the repo, and the add commands report no errors.
The problem is that file sets do a query of Mercurial's repository data base, which knows only about files that are part of the repository or have been added.
One solution is to add all, and then to get rid of the files that you don't like, e.g.:
hg forget 'set:size(">1M") or binary()'
This works, because the query also requires recently added files, even if they haven't been committed yet.

How to tell Mercurial not to look for root from ignored sub-directory

So here's the problem. I have my configuration file in my home directory ~ under Mercurial control. Part of the
|-~
|.hg/...
|-Dev
|-Project1/...
|-Project2/...
.hgrc
.hgignore
I have Dev directory excluded from the source control in .hgignore file.
However when I am in the directory ~/Dev/Project1 Mercurial thinks that I am in the under the source control. If I type hg root in any directory that is in the .hgignore or its sub-directory hg still considers it being a part of repository.
Is it a bug or a feature ?
UPDATE
So, here's the simple experiment one could do from the command line:
% mkdir -p /var/HgTest
% cd /var/HgTest
% hg init
% echo "this is a repository file" >> test.txt
% hg commit -Am "added repo file"
% cat <<EOT >> .hgignore
heredoc> syntax:re
heredoc>
heredoc> ^Dev
heredoc> EOT
% hg commit -Am "added .hgignore"
% echo "This is not in repository" >> Dev/notinrepo.txt
Now, Dev directory not in repository, if you type hg st anywhere under /var/HgTest it shows you that repo is clean. However if you go into Dev directory and type hg root it will output /var/HgTest. This is perhaps desired result. However, since the path should be ignored, I would think that hg root should effectively exit with -1 return code and message "not in repository" or something like that.
In my case, having HOME directory under source control effectively makes some of the tools consider every new directory (even under ignored paths) as a part of Mercurial repository located in the HOME directory.
It's a feature for when you are in ~/Dev/Project1/deeply/nested and want to keep mercurial commands within the scope of Project1.
A workaround is to hg init in ~/Dev/Project1. Part of the problem is the bad practice of putting your home directory under version control; I can see no benefit to be gained from it and much cost. As an example, almost everything you do with a browser, or music player, or many other programs is going to alter files in ~/.groovy-game/config or ~/.browser/cache-files; there is no meaningful way to choose a commit point. Because of this it would be better to establish good, incremental snapshot backups for $HOME, even if they are stored on the same machine.
This is not to say that dot-directories in your home should never be versioned. for example, suppose I hack on my ~/.vim files because I am working on the ultimate editing environment, cd ~/.vim; hg init can certainly be useful.
Put another way — so long as there is an .hg repository somewhere in the tree above you, Mercurial will seek it out and read the ignore file and not take action on ignored paths. However, hg root only looks for an .hg directory. In your case, there is always a root, you are in your ~ repository by definition. I don't see how it could be done otherwise; you can't find the ignore file until you've inspected the root.

Mercurial equivalent of git reset HEAD, or the inverse of hg addremove?

I just moved a bunch of files from one directory to another. I also created several new files. And I ran python setup.py sdist, which generates a bunch of files.
I forgot to add all these files I do not care about to my .hgignore so when I ran hg addremove - bam, I get everything.
Ick.
In git, I would run git reset which would simply unstage all of my changes and life would be happy. In TortoiseHg I could just click the little checkbox a couple of times to uncheck all of them and life would also be happy.
But on the command line... I just want to go back to the way things were before I did hg addremove.
How do I do that?
It's possible to undo add 'adds' and 'removals' in two steps.
First, to undo 'adds':
hg forget "set:added()"
Undoing removals is a bit more tricky, because if we just hg revert the files marked as removed, like `hg revert "set:removed()", these files will pop back in the working directory. This is probably not what you want, so let's delete them right after they are reverted, for Linux it would be something like:
hg locate "set:removed()" | xargs -I % sh -c 'hg revert "%"; rm "%"'

hg rename doesn't delete the original folder in the working copy

I've a directory in Mercurial repository called httpdocs/css/ui-lightness. I want to move this directory and all its contents to httpdocs/css/jquery/themes/ui-lightness. So, I think this is the command to launch:
hg rename httpdocs/css/ui-lightness httpdocs/css/jquery/themes/ui-lightness
In fact, I've already tried and it seems to work, except that in the working copy the "source" directory (that is, httpdocs/css/ui-lightness) is NOT deleted (while in the repository it is).
Can someone explain why?
A Krtek found, what you're doing should work. Here's me running it locally:
~$ mkdir -p httpdocs/css/ui-lightness
~$ cd httpdocs/
~/httpdocs$ hg init
~/httpdocs$ echo test > css/ui-lightness/file
~/httpdocs$ hg commit -A -m "initial commit, old location"
adding css/ui-lightness/file
~/httpdocs$ hg rename css/ui-lightness css/jquery/themes/ui-lightness
moving css/ui-lightness/file to css/jquery/themes/ui-lightness/file
~/httpdocs$ ;s
bash: syntax error near unexpected token `;'
~/httpdocs$ ls
css
~/httpdocs$ tree
.
`-- css
`-- jquery
`-- themes
`-- ui-lightness
`-- file
4 directories, 1 file
~/httpdocs$ hg stat
A css/jquery/themes/ui-lightness/file
R css/ui-lightness/file
If you have any untracked (possibly ignored) files in httpdocs/css/ui-lightness they won't be renamed and thus the directory won't be empty and thus not removed, but the tracked contents in that directory should be moved.
Notice I've not yet committed that rename (and that it shows up as an Add and a Remove even though Mercurial knows it's a rename), but for it to be reflected in other clones, I'd need to hg commit, hg push and they'd have to hg pull and then either hg update or hg merge.
What are you calling the "repository" and the "working copy" and where do you do your hg rename command ?
I think you just forgot to push your changes on one side and then do a pull on the other side. Changes won't magically appear in all the clones of your repository, you must retrieve the changes.
I just tested, hg rename removes the files just fine.
It does not delete the source, it just marks it for removal in the repository. You should remove files by hands afterwards.

Putting home directory under source control (hg)

For a while I had just my code under source control, but I decided it would be cool to put things like my stuff in my .vim folder among other things in a repo. (I'm obviously not going to store everything in my repo, just various config files, my src directory, and maybe a few other things as well)
I was able to set up a repo fine, then push it to my server where I can access it from my other computers, but I can't clone it to my other computers. When I try to clone it it fails because the home directory isn't empty. Is there a way to do what I want here?
Since the versioned files between my computers are the same, what I did was:
~$ hg clone ssh://myserver/hg/dotfiles mydotfiles
~$ mv mydotfiles/.hg .
~$ rm -rf mydotfiles
and that's it, now your home folder is under version control, but of course if your dot files are not identical between computers you will have to do something about it.
Since I only want to version some files and not everything under my home folder, I have this simple rule in ~/.hgignore
# This .hgignore is for the dotfiles repository only,
# the rest of my HG repositories use the file
# .hgignore_global as referenced by [ui]'s ignore setting.
syntax:glob
*
This way I don't get an ocean of files when I do hg status and only see those files that I have under version control that have been modified.
But since I want to see unversioned files when working within another hg repository, I have this in my ~/.hgrc file
[ui]
ignore=/home/gajon/.hgignore_global
And ~/.hgignore_global contains some filters for common transient files:
syntax: glob
*.pyc
*~
.*.swp
.svn*
*.svn*
*.fasl
syntax: regexp
^\.pc/
Suppose you have proj1 and proj2. proj1 is a mercurial repo you want to clone to proj2, but proj2 already has files in it.
Try this:
hg clone proj1 proj3
mv proj3/.hg proj2
rmdir proj3
cd proj2
hg update -C -r tip
In the other directories that already exist, you could hg init, hg add what you want, hg commit that, hg pull from the central repo, and deal with the resulting merge.