How to find changes to a section of code - mercurial

So there is a bug in a bit of code I wrote a long while back. When I went to look into it, it had all been changed! I don't know which colleague changed it. I don't know when it was changed. This file has been changed many, many times. I'm not concerned with everytime this file has a commit. I definitely don't want too look through all 100 commits this file has been in just to find which commits changed this area of code.
I want to find all of the commits that affected file xyz.txt between lines 250 and 300.
Better yet, I want to find all of the commits that affected the function doStuff() in file xyz.txt.
Is that possible?

As torek said, hg blame will do the job.
To filter lines between 250 and 300 you could do:
hg blame -ucd xyz.txt | cat -n | sed -n 250,300p
-u: Show user
-c: Show changeset
-d: Show date

Related

Mercurial: Most recent change per file

I'm looking for a way to make Mercurial output a table like this:
File Most recent revision changing the file Date of that revision
==== ====================================== =====================
foo.py 44159adb0312 2018-09-16 12:24
... ... ...
This is just like github does it on the "Code" overview page. (screenshot from torvalds/linux):
"Most recent" could refer the date or to the DAG hierarchy relative to the current changeset, or maybe to the current branch. Perhaps the latter is more useful, but in my particular use case, it doesn't make a difference.
I'd also like to be able to provide a list of files or a subdirectory for which I want the table. (I don't necessarily want it for everything)
I am aware that I could do it using a small script, looping over hg log -l 1 <file>, but I was wondering if there is a more efficient / more natural solution.
You won't get around looping over all files. Yet with hg manifest you get that list of files. Then template the output as needed:
for f in $(hg ma); do hg log -l1 $f -T"$f\t\t{rev}:{node|short}\t\t{date|isodate}"; done
This gives output like
.hgignore 38289:f9c426385853 2018-06-09 13:34 +0900
.hgsigs 38289:f9c426385853 2018-06-09 13:34 +0900
.hgtags 38289:f9c426385853 2018-06-09 13:34 +0900
You might want to twiddle more with the output formatting. See the mercurial wiki for a complete overview of output templating.
Git will follow the commit DAG, because that's all it has. In Mercurial, you have (many) more options because you have more data.
Probably the ideal option here is follow(file, .) (combined with first or last as appropriate). But as hg help revset will tell you, you have the following options (I've shrunk the list to the obvious applicable ones):
ancestors(set[, depth])
Use this with the set being . to get ancestors of the current commit, for instance, if you want to do DAG-following a la Git. Or, use ::., which is basically the same.
branch(string or set)
Use this with . to get all commits in the current branch. Combine with other restrictors (e.g., parents) to avoid looking at later commits in the current branch if you're not at the tip of the current branch.
file(pattern)
Use this with a glob pattern to find changesets that affect a given file.
filelog(pattern)
Like file but faster, trading off some accuracy for speed (see documentation for further details).
follow([file[, startrev]])
To quote the documentation:
An alias for "::." (ancestors of the working directory's first parent).
If file pattern is specified, the histories of files matching given
pattern in the revision given by startrev are followed, including
copies.
modifies(pattern)
Use this (with any pattern, not just glob) to find changesets that modify some file or directory. I think this is limited to M type modifications, not addition or removal of files, as there is also adds(pattern) and removes(pattern). Use all three, or-ed together, to find any add/modify/remove operations.
first(set, [n])
last(set, [n])
limit(set[, n[, offset]])
Use this to extract a particular entry out of the revset.
When searching forwards (the default), last(follow(file, .)) seems to work nicely to locate the correct revision. As you noted, you have to do this once per file—it will definitely go faster if you write your own Mercurial plug-in to do this without reloading the rest of the system all the time.
Somehow more efficient / more natural solution can be:
create template|style for desired log output (I can't predict, which way will be better for you)
create alias for hg log -l 1 --template ... or hg log -l 1 --style ...
EDIT
A lot later, more correct solution (from recent discoveries) with hg grep
hg grep "." "set:**.py" --files-with-matches -d -q -T"{files % '{file} {date|age}\n'}"
Part of output in test-repo
hggit/__init__.py 7 weeks ago
hggit/git_handler.py 7 weeks ago
hggit/gitdirstate.py 7 weeks ago
…
You have to modify fileset in order to get results only for part of your tree (for all branches) and, maybe, template in order to fulfill your needs.
I didn't have fileset for selecting "files in branch X" just now, I think, it will be something using revs() predicate
"revs(revs, pattern)"
Evaluate set in the specified revisions. If the
revset match multiple revs, this will return file matching pattern in
any of the revision.
because some not published predicates (according to examples, see # "set:revs('wdir()'..." for referencing working directory) can be used for defining revset and I can't discover/predict the correct form for branch predicate

How can I see changesets that remove a match *in a specific branch*?

As hg help grep says:
By default, grep prints the most recent revision number for each file in
which it finds a match. To get it to print every revision that contains a
change in match status ("-" for a match that becomes a non-match, or "+"
for a non-match that becomes a match), use the --all flag.
This works as advertised: When I run hg grep --all pattern, I get a list of hits marked with :+: or :-::
plaintext.py:8055:+: ...
plaintext.py:4690:-: ...
otherfile.py:4690:-: ...
plaintext.py:4630:+: ...
plaintext.py:4630:+: ...
The problem is when I try to restrict the search to a branch or revset:
hg grep --all -r 'branch(default)' pattern
The above will no longer print the revisions in which there is a change of status. Lots of revisions that match are printed (not just the most recent or most ancient one), and many revisions that removed a match (marked with :-:) are no longer printed. (Some :-:-revisions are still printed; I don't understand when this happens.)
This seems like it could be a bug, but what do I know. I'm using mercurial 4.2 (on OS X).
I could live with filtering the output of unrestricted hg grep --all; but the default format does not include the branch (and I do know know enough to write a template that includes all the current information plus the branch).
I think you're running into this bug:
https://bz.mercurial-scm.org/show_bug.cgi?id=3885
Unfortunately "hg grep" is notoriously buggy and likely needs lots of work to get into a place where it's more usable.

auto-accepting a Mercurial change chunk

I have a very large repo with thousands of files that can regularly get updated by automatic processes that are out of my control (this is for Unity 3D, for what it's worth).
For example, if I upgrade Unity to a new version, it will reimport all textures and maybe add a line in thousands of .meta files that correspond to a new serialized data that didn't exist previously.
Obviously reviewing thousands of files is terrible. Most of the time though, I can quickly identify a particular diff, and would just like to automatically check all the files that have the same diff, commit to get them out of the way, and see what's left: other diffs that I might not know about.
For example I just commited 4000+ files that all contained this diff:
So the pattern would be easy to find:
- textureFormat: -5
+ textureFormat: -1
I suppose I could write a script, or a TortoiseHg tool to do that, I just have no idea where to begin. I'd need to iterate over all changed files/chunks, match a pattern, commit the chunks...
I know of no tool to do exactly what you want. However I believe it's relatively easy to write a small bash script for such or use the command line:
hg diff --nodates --noprefix -U 0 | grep '^+' | grep -v '+++' | sort | uniq -c
will list you the inserted lines of the current diff in descending order of the number of occurences, thus the most frequently occurring diff first.
With that list you get a list of files which match the newly inserted pattern, for instance
hg files "set:grep('^ textureFormat: -1')"
should give you all files with that pattern (whether it's new or not, though). You probably want to check those files, whether their diff contains anything else:
hg diff "set:grep('^ textureFormat: -1')"
Now you can make use of the results and even exclude single files, if the diff output didn't suit you:
hg commit "set:grep('^ textureFormat: -1') and not 'unwantedFilename.cpp'"
In the above commands I made use of the fileset capability and of hg grep which accepts regular expressions. Check hg help grep, hg help fileset and hg help patterns for a more in-depth explanation.

How to annote a large file with mercurial?

i'm trying to find out who did the last change on a certain line in a large xml file ( ~100.00 lines ).
I tried to use the log file viewer of thg but it does not give me any results.
Using hg annote file.xml -l 95000 .. took forever and eventually died with an error message.
Is there a way to annote a single line in a large file that does not take forever ?
You can use hg grep to dig into a file if you have interest in very specific text:
hg grep "text-to-search" file.txt
You will likely need to add the --all switch to get every change that matches and then limit your results to a specific changeset range with -r firstchage:lastchange syntax.
I don't have a file on hand of the size you are working with, so this may also have trouble past a certain point, particularly if the search string matches many many lines in the file. But if you can get as specific as possible with your search string, you should be able to track it down.

Using Mercurial, how can I measure individual contributions?

My team is using Mercurial, and I would like to know the relative contributions by each team member. I know that we cannot measure productivity by lines of code, but I would like to see if each person at least contributed something, even if it was overwritten by others later. So, I don't just want to see who is responsible for the current version (a la Mercurial annotate), but to do this recursively through all revisions, ideally with output that can be easily visualized or dumped into a spreadsheet.
Any tips?
There's an extension exactly for this, named churn, it is bundled with Mercurial, but not automatically enabled. You can find more information here: ChurnExtension.
In your mercurial.ini file, to the [extensions] section, add the following:
[extensions]
churn=
Then to look at the churn of your repository, just do:
hg churn
This will output something like this (this is for the Noda-Time project):
[C:\Dev\VS.NET\Noda-Time-docs] :hg churn
skeet#pobox.com 296444 *************************************************************************************************************
james.keesey#gmail.com 203877 ***************************************************************************
James Keesey 80466 ******************************
dmitry.bulavin#gmail.com 25552 *********
Dmitry Bullavin 17657 ******
martinho.fernandes#gmail.com 16325 ******
Dmitry Bulavin 4273 **
james.keesey 2650 *
matt.scharley 768
configurator 450
lasse#vkarlsen.no 64
TeamCity#Nordrassil 2
Churn does the job, but note that it if a user moves files a lot, he will have huge amounts of changed lines. I just did a test, here are the results:
C:\Projects\personal\test>hg churn
darius.damalakas#gmail.com 10 *****************************************
C:\Projects\personal\test>hg mv a.a b.b
moving a.a to b.b
C:\Projects\personal\test>hg commit -m "moving 10 lines to another location"
b.b
committed changeset 1:c54200557152
C:\Projects\personal\test>hg churn
darius.damalakas#gmail.com 30 *****************************************
Note that I have only created 10 lines, but for moving a file I got 20 line changes. That does not convey a good picture.