Mercurial - how to see the history for a specific line of code - eclipse

I have a CSS file with thousands of lines of code. I want to see when a specific line/chunk of code was changed, without going back and reviewing each revision that changed this file (that will take a looooong time!)
Is there a way, using either TortoiseHg, Eclipse with a Mercurial plugin, or command-line, to view the history of a specific piece of code?

The correct answer is hg grep (Mercurial grep page).
More deep:
hg grep --all "PATTERN" FILENAME
Sample output:
>hg grep --all "textdomain" functions.php
functions.php:2:-:load_theme_textdomain('fiver', get_template_directory() . '/translation');
functions.php:2:+:load_theme_textdomain('fiver', get_template_directory() . '/languages');
functions.php:1:+:load_theme_textdomain('fiver', get_template_directory() . '/translation');
(in order - filename, revision, action, string in this revision)

You can use:
hg annotate <file>
to find out in which revision line was changed and then use same command with -r <revision> at the end to go backwards through revisions.

I don't think there is an option to view a specific part of a file. But to see the differences of the total file over several revisions you can use hg diff:
hg diff -r firstrevisionnumber:otherrevnumber filename
For example, hg diff -r 0:8 screen.css
Or the command hg log screen.css.

Use hg histgrep --all PATTERN FILENAME (used to be hg grep in the older versions, and that doesn't work anymore)

Related

Mercurial: Converting existing folders into sub-repos

I have a Mercurial repository that looks like this:
SWClients/
SWCommon
SWB
SWNS
...where SWCommon is a a library common to the other two projects. Now, I want to convert SWCommon into a sub-repository of SWClients, so I followed the instructions here and here. However, in contrast to the example in the first link I want my sub-repository to have the same name as the folder had at the beginning. In detail, this is what I have done:
Create a file map.txt as follows
include SWCommon
rename SWCommon .
Create a file .hgsub as follows
SWCommon = SWCommon
Then run
$ hg --config extensions.hgext.convert= convert --filemap map.txt . SWCommon-temp
...lots of stuff happens...
Then
$ cd SWCommon-temp
$ hg update
101 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ cd ..
$ mv SWCommon SWCommon-old
$ mv SWCommon-temp SWCommon
$ hg status
abort: path 'SWCommon/SWCommon.xcodeproj/xcuserdata/malte.xcuserdatad/xcschemes/SWCommon.xcscheme' is inside nested repo 'SWCommon'
...which is indeed the case, but why is that a reason to abort? The other strange thing is that if I do not do that last 'mv' above and I execute an 'hg status' then, I end up with lots of 'missing' files in SWCommon as you would expect. The example in the link never makes it this far and basically stops on the hg update above? How do you make it work in practice?
Not currently possible. You could create a new repo converting the original one like:
$ hg --filemap excludemap.txt SWClients SWClients-without-SWCommon
With a excludemap.txt like:
exclude "SWCommon"
And then add the subrepo there.
$ hg --filemap map.txt SWCommon SWClients-without-SWCommon/SWCommon
$ cd SWClients-without-SWCommon
$ hg add SWCommon
$ hg ci -m "Created subrepo"
See the mailing list thread that discusses this problem.

How to find out what commit a checked out file came from

When I check out a file with git checkout $commit $filename and I forget $commit but still remember $filename, how do I find out what $commit was?
First a non-git answer. Check your shell command history. Well, if you didn't use a shell with command history then you don't...
The git answer. You generally cannot find THE $commit. Generally the same contents might have been part of many commits and I don't think git keeps a log of what single file you have checked out (it keeps a log of previous values of HEAD)
Here is a brute force script git-find-by-contents. Call it with your $filename as parameter, and it will show you all commits where this file was included. As the name says
it searches by contents. So it will find files with any name, as long as the contents matches.
#! /bin/sh
tmpdir=/tmp/$(basename $0)
mkdir $tmpdir 2>/dev/null
rm $tmpdir/* 2>/dev/null
hash=$(git hash-object $1)
echo "finding $hash"
allrevs=$(git rev-list --all)
# well, nearly all revs, we could still check the log if we have
# dangling commits and we could include the index to be perfect...
for rev in $allrevs
do
git ls-tree --full-tree -r $rev >$tmpdir/$rev
done
cd $tmpdir
grep $hash *
rm -r $tmpdir
I would not be surprised if there is a more elegant way, but this has worked for me a couple of times in similar situations.
EDIT: a more techy version of the same problem appears here: Which commit has this blob?
I don't think you can. Git just loads that version of the file into the index and your working dir. There is no reference keeping track of what version it loaded
If this is something that you do often, you could write up a script that could do it using git diff --cached and march through all the commits that changed that file.
You can use
git log <filename>
to find out which commit you want to checkout
If you're lucky, you could maybe try a non-git way:
history | grep "git checkout.*filename"
Try this (untested ;):
$ for commit in $(git log --format=%h $filename); do
> if diff <(git show $commit:$filename) $filename >/dev/null; then
> echo $commit
> fi
> done
Simple and elegant:
$ git log -S"$(cat /path/to/file)"
Only works if the content is unique, then again that's the same for the hash-comparison answers that came before.
It also displays only the first version that matches, rather than all.
Here are the details of a script I polished up as the answer to a similar question, and here you can see it in action:
(source: adamspiers.org)

How to print the contents of all revisions of a particular file in Mercurial?

Let's say I have 5 revisions of a README file. How do I view them all in Mercurial?
It would be nice to be able to limit the output, similar to:
hg log -l 10
I'm using PowerShell, so combined solutions are also welcome.
I don't know Powershell syntax, but you're looking for the hg cat command. Combined with the answer to your other question I would do it like this in a Unix shell (zsh in my case):
for r in $(hg log --template '{rev} ' README); do hg cat -r $r README; done
I first get all the revisions in which README was changed. They will be put into a big string like this:
% hg log --template '{rev} ' README
822 804 688 681 629 539 538
You then iterate over these revision numbers and call hg cat on each.

How do I revert a big change in CVS?

One of my colleagues has totally messed up the contents of a directory in our main CVS repository. I need to just revert the whole module to the state it was in at the end of last year. What's the CVS command to do this please?
He has added and removed hundreds of files, so a simple "copy over files from old checkout and commit" isn't enough.
I have RTFM and STFW, and I tried this:
cvs co modulename # Note no -P option
cvs up -jHEAD -jMAIN:2008-12-30 modulename
But that doesn't work - the new files he created get removed, but the old files and directories don't get resurrected. (I didn't commit it).
I can probably write a shell script for this, but surely this functionality must be in CVS already?
Update: Some clarifications:
I can get a local checkout of the module at a specific date. The question is how to get that back into CVS.
I do have backups, but the point using of a revision control system like CVS is that it's supposed to be easy to get any historical state. Next time something like this happens I may not be lucky enough to have backups (e.g. backups are daily, so I may lose up to a day's work).
I know that CVS is old, and we should move to something newer. But in a large team with a large number of CVS-based tools (checkout & build scripts, nightly build server, etc) the time cost of such a move is considerable. (Evaluation, updating scripts, testing, migration, training, lost developer time, maintaining both systems in parallel as CVS would still be needed for old branches). Hence this has to be planned & scheduled by management.
Update #2: I'm going to start a bounty on this. To qualify for the bounty you have to explain how to revert using normal CVS commands, not with a hacky shell script.
Update #3: The server is CVS 1.12.13. Access is via pserver. I can use the same version of CVS on a Linux PC, or the CVSNT 2.0.51d client on Windows.
Actually your initial approach was very close to the solution. The problem is, that joining date-based does not handle removed files and directories correctly. You need to set a tag to the code base you want to join first:
mkdir code_base1 && cd code_base1
cvs co -D "2008-12-30" modulename
cvs tag code_base_2008_12_30
Now do the join tag-based, subtracting all changes between now and 2008-12-30:
cd .. && mkdir code_base2 && cd code_base2
cvs co modulename
cvs update -d -j HEAD -j code_base_2008_12_30 # use -d to resurrect deleted directories
Compare the contents of code_base1 and code_base2. They should be identical except for the CVS meta information. Finally commit the code as it was on 2008-12-30 as new HEAD:
cvs commit -m "Revert all changes this year"
Note that tagging the code you wish to join like this will not work, because rtag also does not handle removed files and directories correctly, when using -D:
cvs rtag -D "2008-12-30" code_base_2008_12_30 modulename
There are several problems with CVS and you're hitting them with such a problem.
CVS is file-oriented, no concept of a changeset or snasphot. That means that changes such as the one you want to revert are a bit difficult to handle. Commits are atomic within a given directory, not outside.
Directories are not versioned. That means that empty directories will be deleted (if you update with -P) and that you have to specify -d to create them on checkout/update.
So, to answer your question, dates are probably the only way to deal with because you didn't use tags to create some poor man's version of changeset.
My comment about backups is that it may be easier to recover the whole repo from backups than try to correct things that CVS is not really good at.
I would encourage you -- but that is another subject -- to change version control as soon as you can. Trust me, I've been dealing with CVS for a long time within the FreeBSD project and learn very quickly how hateful CVS is... See here for some of my views on version control software.
I believe your second command should also be a checkout, rather than an update. I can't justify this with logic, since there is no logic in the world of CVS, but it has worked for me. Try this:
cvs co -P modulename
cvs co -P -jHEAD -jMAIN:2008-12-30 modulename
If you're reverting a branch other than HEAD, e.g. X, pass the -rX argument in both commands:
cvs co -P -rX modulename
cvs co -P -rX -jHEAD -jMAIN:2008-12-30 modulename
I'm still interested to know if there's an easier way. (There must surely be an easier way). What I ended up doing was, on a Linux PC using bash:
# Get woking copy we're going to change
cd ~/work
rm -rf modulename
cvs up -dP modulename
cd modulename
# Remove all files
find . -name CVS -prune -o -type f -print | xargs cvs rm -f
# Get the old revision
cd ~
mkdir scratch
cd scratch
cvs -q co -D 2008-12-31 modulename
cd modulename
# Copy everything to the working dir and do "cvs add" on it
find . -name CVS -prune -o -type f -print | \
xargs tar c | \
(cd ~/work/modulename && tar xv | \
xargs cvs add)
# Check everything is OK before we commit
cd ~/work/modulename
cvs -nq up
# it gave me an error on readme.txt because I'd deleted and then added it, so:
mv readme.txt x # save good rev
cvs add readme.txt # resurrect the bad rev
mv x readme.txt # clobber file with good rev
# Commit it
cvs commit -m "Revert all changes this year"
# Delete now-empty directories
cvs -q up -dP
# Double-check everything is back how it was
diff -ur -xCVS ~/scratch/modulename ~/work/modulename
Then I discovered that there were still differences - my colleague had added filenames containing spaces, which weren't deleted by the above process. I had to delete those separately. (I should have used find ... -print0 rather than -print, and passed the -0 argument to xargs. I just didn't realise there were files with spaces.)
You could look into cvsps. Google it.
Also, with quilt (or Andrew Morton's patchscripts, which is what quilt started out as) and cvsps, a very close approximation of changesets can be had.
see http://geocities.com/smcameron/cvs_changesets.html
Have you tried using the -d option? (build subdirectories)
As far as I can remember, it's implied for cvs co, but not for cvs up.
According to http://www.astro.ku.dk/~aake/MHD/docs/CVS.html, the following is what you need:
cvs update -D "30 Dec 2008 23:59"
Big problem, don't have full answer, just a tip on your scripting to deal with spaces in file names.
Instead of
find ... | xargs tar c - | ...
try putting
find ... | perl -e '#names = <>;' -e 'chomp #names;' -e 'system( "tar", "c", "-", #names);' | ...
that way, your archive creation (or similar operations) won't suffer from spaces in the names, the shell argv parsing gets skipped before tar is called.
One more thing, on the off chance it actually works: if there is a CVS to SVN utility, use it (I am assuming such a utility would pull deleted files from the "CVS attic"), and if it saves each moment in time as a project level checkpoint (since SVN does that, unlike CVS), use SVN to fetch the right moment in time. Lot of ifs...
If you or a colleague are comfortable with git, you could use git cvsimport to create a git repository mirroring the CVS repository. Reverting a commit/changeset in git is trivial (using git revert). You could then use git cvsexportcommit to send the revert commit to CVS.
This might all sound overly complicated, but in my experience git cvsimport and git cvsexportcommit work really well once you've got everything set up. You end up with all the power of git personally even though the project is still using CVS.
If you have a backup of your repository (the actual RCS files on the server, e.g. on tape) you could just restore that folder on the CVS server to the state it was before. Don't forget to stop the CVS server before doing this (and restart it afterwards).

How do I get a list of commit comments from CVS since last tagged version?

I have made a bunch of changes to a number of files in a project. Every commit (usually at the file level) was accompanied by a comment of what was changed.
Is there a way to get a list from CVS of these comments on changes since the last tagged version?
Bonus if I can do this via the eclipse CVS plugin.
UPDATE: I'd love to accept an answer here, but unfortunately none of the answers are what I am looking for. Frankly I don' think it is actually possible, which is a pity really as this could be a great way to create a change list between versions (Assuming all commits are made at a sensible granularity and contain meaningful comments).
I think
cvs -q log -SN -rtag1:::tag2
or
cvs -q log -SN -dfromdate<todate
will do what you want. This lists all the versions and comments for all changes made between the two tags or dates, only for files that have changed. In the tag case, the three colons exclude the comments for the first tag. See cvs -H log for more information.
The options for the cvs log command are available here. Specifically, to get all the commits since a specific tag (lets call it VERSION_1_0)
cvs log -rVERSION_1_0:
If your goal is to have a command that works without having to know the name of the last tag I believe you will need to write a script that grabs the log for the current branch, parses through to find the tag, then issues the log command against that tag, but I migrated everything off of CVS quite a while ago, so my memory might be a bit rusty.
If you want to get a quick result on a single file, the cvs log command is good. If you want something more comprehensive, the best tool I've found for this is a perl script called cvs2cl.pl. This can generate a change list in several different formats. It has many different options, but I've used the tag-to-tag options like this:
cvs2cl.pl --delta dev_release_1_2_3:dev_release_1_6_8
or
cvs2cl.pl --delta dev_release_1_2_3:HEAD
I have also done comparisons using dates with the same tool.
I know you have already "solved" your problem, but I had the same problem and here is how I quickly got all of the comments out of cvs from a given revision until the latest:
$ mkdir ~/repo
$ cd ~/repo
$ mkdir cvs
$ cd cvs
$ scp -pr geek#avoid.cvs.org:/cvs/CVSROOT .
$ mkdir -p my/favorite
$ cd my/favorite
$ scp -pr geek#avoid.cvs.org:/cvs/my/favorite/project .
$ cd ~/repo
$ mkdir -p ~/repo/svn/my/favorite/project
$ cvs2svn -s ~/repo/svn/my/favorite/project/src ~/repo/cvs/my/favorite/project/src
$ mkdir ~/work
$ cd ~/work
$ svn checkout file:///home/geek/repo/svn/my/favorite/project/src/trunk ./src
$ cd src
$ # get the comments made from revision 5 until today
$ svn log -r 5:HEAD
$ # get the comments made from 2010-07-03 until today
$ svn log -r {2010-07-03}:HEAD
The basic idea is to just use svn or git instead of cvs :-)
And that can be done by converting the cvs repo to svn or git using cvs2svn or cvs2git, which we should be doing anyway. It got my my answer within about three minutes because I had a small repository.
Hope that helps.
Something like this
cvs -q log -NS -rVERSION_3_0::HEAD
Where you probably want to pipe the output into egrep to filter out the stuff you don't want to see. I've used this:
cvs -q log -NS -rVERSION_3_0::HEAD | egrep -v "RCS file: |revision |date:|Working file:|head:|branch:|locks:|access list:|keyword substitution:|total revisions: |============|-------------"