Mercurial keywords extension to expand on every commit - mercurial

I need to use the hg keyword extension to embed the build date and revision into a source file. Leaving aside the whole "you really don't want to be doing that" argument, how can I do this?
Here's what my source file (lib/foo/version.rb) looks like (which happens to be Ruby, but that's only relevant from the point of view that I don't have a "compile" step in my build which I could do a -DREVISION="$(hg id)" in):
module Foo
VERSION = {
:date => "$Date$",
:changeset => "$Revision$"
}
end
The problem is that $Revision$ and $Date$ are expanded with the changeset and commit date of that file, whereas what I need is the tip changeset and commit date of the whole repository.
I don't see an obvious template I can use in hg help templates, nor does the keyword extension mention anything with global scope. Is what I'm trying to do possible?

You can install a post-commit hook that updates the file:
[hooks]
post-commit = sed -i lib/foo/version.rb \
-e "s|\$Date.*\$|\$Date: $(date)\$|" \
-e "s|\$Version.*\$|\$Version: $(hg id -i)\$|"
You should then probably add the version file to the .hgignore file -- it will change after every commit and thus always be dirty. You could also add a encode filter that will clean up the version file:
[encode]
lib/foo/version.rb = sed -e "s|\$Date.*\$|\$Date\$|" \
-e "s|\$Version.*\$|\$Version\$|"
This script will make Mercurial see the file as clean -- no matter what date and changeset has it really contains, Mercurial will see it as containing un-expanded $Date$ and $Version$ keywords:
$ hg commit -m test
$ hg tip
changeset: 7:df81c9ddc9ad
tag: tip
user: Martin Geisler
date: Wed Apr 06 14:39:26 2011 +0200
summary: test
$ hg status
$ hg cat version.py
date = "$Date$"
version = "$Version$"
$ cat version.py
date = "$Date: Wed Apr 6 14:39:26 CEST 2011$"
version = "$Version: df81c9ddc9ad$"

If you're running your code from a checkout you can invoke hg directly and cache the value. Something like:
module Foo
VERSION = {
:version => system("hg log --template '{note|short}-{latesttag}-{latesttagdistance}' -r .")
}
end
and if you're not running the code from inside a checkout on a system with Mercurial installed, then your deploy script can easily get/use the value -- perhaps by using hg archive to get the tarball to send which then automatically includes a .hg_archive.txt.
I guarantee you there's a prettier way to do this than the keywords extension no matter what your setup is.

Related

How to create patches for all changes to a file in a branch

A number of changes have been made to a file in a branch. Some of the changes were for a feature that now needs to be implemented on the main branch.
How can I create a patch for each revision of the file so that I can choose which changes I wish to apply to my main branch? Ideally I would like each patch to contain the description of the revision too for reference purposes.
You can get a log for just one file by quoting that file in the log command:
hg log FILENAME
and you can get the diff for a certain revision and filename by giving both as argument to log, asking it to give you the patch (I've configured my hg to use the git-style patch format by default - maybe that's needed here, too):
hg log --patch --rev XXX FILENAME
if you want a patch for each revision FILENAME was changed, you could try in bash something like
for rev in $(hg log -T"{rev}\n" FILENAME); do hg log -p -r${rev} FILENAME > FILENAME.${rev}.patch; done
which yields you the typical patches, including the commit message used:
$ cat FILENAME.15.patch
Änderung: 15:441bead3e0b3
Vorgänger: 7:36479da8f266
Nutzer: planetmaker <email#example.org>
Datum: Tue Feb 10 22:58:24 2015 +0100
Zusammenfassung: More boo
diff --git a/FILENAME b/FILENAME
--- a/FILENAME
+++ b/FILENAME
## -1,1 +1,2 ##
foo is boo!
+Even moar booo!

Mercurial: Identify file name after rename

Mercurial tracks the contents of a file throughout renames (hg mv $OLD $NEW), so that hg annotate $NEW also shows up the line-wise changes formerly made to $OLD with their original identification. That works fine.
BUT there seems no straightforward way to find out the name of the $OLD file, to which some given line has belonged within the ancestry of $NEW. hg annot $NEW -r$REV only works down to the rename changeset.
Of course the information is somehow accessible, e. g. by crawling through hg log (without --follow) and identifying the renames with some hg log -r$RENAMEREV -g -p (or by clicking through hg serve's web interface).
But this “workflow” is not only annoying and error-prone, but [most importantly] it isn't non-interactive/scriptable.
My question: Is there a way to get/construct either
some list of the file name history of $NEW (best with respective revision ranges), or
the name of the file in which line $LINE was commited (some kind of filename option for hg annot)?
Ideas in either the hg CLI or Python/hglib appreciated.
Either include the {file_copies} keyword in your hg log template:
$ hg init demo
$ cd demo
$ touch a
$ hg ci -Am 'file a'
adding a
$ hg mv a b
$ hg ci -Am 'moved to file b'
$ hg log -r . -T"{file_copies}\n"
b (a)
The built-in template status will include file copy info when you set the --copies flag:
$ hg log -r 1 -Tstatus --copies
changeset: 1:b37952faaddc
tag: tip
user: Martijn Pieters <mjpieters#fb.com>
date: Sun Jul 31 16:07:04 2016 +0100
summary: moved to file b
files:
A b
a
R a
So file b was taken from a.
See hg help template for more things you can include in log output.

Sort hg status by date

Im looking for a convenient way to sort the ouput of
hg status
to see the newest file at top.
Here is a possible solution that will only work in Linux-like environments (I am trying it in Windows using MSYS). You could use ls to list files sorted by time, passing it the output of hg status:
$ hg st
M modified.txt
A added.txt
R removed.txt
? unknown.txt
$ ls -t1 `hg st -n -a -m -u`
unknown.txt
modified.txt
added.txt
Using this method you lose the MAR?... status, but it shows the files that are changed, added, or are untracked, sorted by modification time. However, it does kind of rely on your allowed parameters to ls.
Effectively you're using the backquoted mercurial command to provide a list of filenames to ls, which will do the sorting for you. Don't think there's a simple way to do this in vanilla Windows. Possibly by using a for loop?
First, create a file with this content:
changeset = "{files}"
file = "{file}\n"
Let's say you call it sorted.txt and put it in your home directory. Then you can give this command:
hg -q outgoing --style ~/sorted.txt | sort -u

Find what mercurial commit changed the executable bit on a file

I know how to use hg blame to find what exact commit changed a line in a file, but I can't find a similar way to find when the executable bit on a file was changed.
First note that as changes in the exec bit don't affect the file contents, like deletions, they will not necessarily be shown by 'hg log filename'. (Unix nerds can compare the ctime/mtime rules for files and directories with respect to rm/chmod to understand this distinction.) So you will need to use something like:
$ hg log --deleted file
to show all changesets that touch a file, include exec changes, deletions, and duplicates. This is not enabled by default for various reasons including that it can be an order of magnitude slower.
Finding exec bits while perusing the log will also mean looking at git-style patches as standard patch(1)-compatible patches don't know about exec bits. So the total command might look something like:
$ hg log --removed -pg contrib/simplemerge | grep "^new mode" -B 10
+ import os
sys.exit(main(sys.argv))
changeset: 4363:2e3c54fb79a3
user: Alexis S. L. Carvalho <alexis#cecm.usp.br>
date: Mon Apr 16 20:17:39 2007 -0300
summary: actually port simplemerge to hg
diff --git a/contrib/simplemerge b/contrib/simplemerge
old mode 100644
new mode 100755
That reads: "search all git patches on simplemerge for lines starting with 'new mode' and show the previous 10 lines".
Another alternative is to use bisect. This can be used for finding basically any sort of change you can test for. For instance, if you're looking for where the X bit is set:
$ hg bisect -g 1000 # some past revision without the X bit
$ hg bisect -b tip # some recent revision with the X bit
Testing changeset 8114:ad3ba2de2cba (14179 changesets remaining, ~13 tests)
993 files updated, 0 files merged, 716 files removed, 0 files unresolved
$ hg bisect -c "[ ! -x contrib/simplemerge ]" # shell expression returns 0 (good) if no x bit
Changeset 8114:ad3ba2de2cba: bad
Changeset 4566:087b3ae4f08a: bad
Changeset 2797:a3c6e7888abf: good
Changeset 3678:7e622c9a9707: good
Changeset 4121:d250076824e3: good
Changeset 4345:ec64f263e49a: good
Changeset 4454:28778dc77a4b: bad
Changeset 4403:15289406f89c: bad
Changeset 4371:d7ad1e42a368: bad
Changeset 4355:10edaed7f909: good
Changeset 4366:390d110a57b8: bad
Changeset 4363:2e3c54fb79a3: bad
Changeset 4361:99c853a1408c: good
Changeset 4362:465b9ea02868: good
The first bad revision is:
changeset: 4363:2e3c54fb79a3
user: Alexis S. L. Carvalho <alexis#cecm.usp.br>
date: Mon Apr 16 20:17:39 2007 -0300
summary: actually port simplemerge to hg
Here we've automated the test with a standard Bourne shell expression to check a file's exec bit and Mercurial then runs through checking out revisions and testing them for us.
There may not be a simple build-in method a la hg blame (or maybe there is, and I just don't know it!), but you should be able to use a brute-force approach to hunt down the changeset by looking at the diffs.
First, you need to enable git-style diffs, because hg's normal diff output does not show changes in file modes. To do this, add to your hgrc the following:
[diff]
git = True
Then, you can look at all the diffs for the file you're interested in and find the commit that changed the mode, by running:
hg log -p file_of_interest
If you're on a *nix system, it helps to pipe to less or grep to easily search through the output. The mode changes should be displayed just below the beginning of the patch line that starts with:
diff --git a/file_of_interest b/file_of_interest
old mode ....
new mode ....
So, for instance, you might be looking for a mode change from 644 (no exec) to 755 (exec bit set), in which case you'd see something like:
old mode 100644
new mode 100755
Once you find the diff you're after that contains the mode change you're after, you can search backwards to get the commit hash.
Not quite as simple as hg blame, but it should work to some degree. Hope that helps.

Is there an equivalent to git's "describe" function for Mercurial?

I'm currently adding packaging to a something that is maintained in Mercurial. Currently the version is defined in the Makefile. I would like to change this so I can build daily packages or properly versioned packages.
Git provides a use "describe" function that can give you a description of the closest tagged build and current revision. For example if I run this in the kernel:
git describe HEAD
GIT returns:
v3.0-rc7-68-g51414d4
telling me that the revision is later than v3.0-rc7, with a git commitish of 51414d4
Is there something similar I can do in Mercurial?
Maybe something like this?
hg log -r . --template '{latesttag}-{latesttagdistance}-{node|short}\n'
Of course you should make an alias for that with AliasExtension.
Note however, unlike "git describe", this command will always show the "latesttagdistance" and "node|short" parts, instead of omitting them when latesttagdistance is 0.
This is a close emulation of git describe:
hg log -r . -T "{latesttag}{sub('^-0-.*', '', '-{latesttagdistance}-m{node|short}')}"
The {sub(...)} function ensures that a working copy that's exactly at tag v0.1.0 will show up as v0.1.0 and not v0.1.0-0-m123456789abc.
Note that the m before the hash is for mercurial, similar to the way git describe uses a g for git.
For convenience, create an alias by adding the following to your ~/.hgrc:
[alias]
describe = log -r . -T "{latesttag}{sub('^-0-.*', '', '-{latesttagdistance}-m{node|short}')}"
Then use the alias by simply typing hg describe.
If you'd like to emulate git describe --dirty, things get even messier – but you can still hide it all in an hg alias:
[alias]
describe = !
dirtymark=;
case " $1 " in " --dirty ") dirtymark=-dirty; ;; esac;
echo $($HG log -r . --template "{latesttag}-{latesttagdistance}-m")$($HG id -i) |
sed -r -e "s/\+\$/${dirtymark}/" -e 's/-0-m[[:xdigit:]]+//'
Now running hg describe --dirty will produce strings like:
v0.1.0
v0.1.0-dirty
v0.1.0-1-mf6caaa650816
v0.1.0-1-mf6caaa650816-dirty
Omitting the --dirty option means that you'll never get a -dirty suffix like (2) and (4), even when the working copy contains uncommitted changes.