views:

347

answers:

3

I would like to retrieve logs from a mercurial repository using mercurial commands api. Unfortunately, mercurial.commands.log prints the messages to the stdout, instead of returning some nice list of revisions, like e.g. pysvn does. Can the be achieved easily? I would like to add mercurial support to my program and would like to do this as easily, as it's possible.

+2  A: 

The simple answer is to use ui.pushbuffer() right before you call the log command and log_output = ui.popbuffer() right after you call it. By doing that log_output will contain the output of the log command.

Are you actually looking for the straight log output though, or do you really want the diff or some other kind of data? If we know what exactly you're trying to get (for example: "the commit messages of every changeset between X and Y") we might be able to show you a better way.

EDIT: Take a look at the Mercurial API wiki page to see how to get most of the common information from repo and ctx objects.

Steve Losh
I don't want to get log messages, because I would have to parse them - not the thing I would like to do. Instead, I would like to get some object, that will hold information about each revision. pysvn returns list of dictionaries, each with date, author, message, etc.
gruszczy
+6  A: 

You should do something along the lines of this:

from mercurial import ui, hg
u = ui.ui()
repo = hg.repo()
for rev in repo:
    print repo[rev]

the subscripted object is a context object. It has useful methods like description(), branch(), and user(). For a complete list of what it can do, see the source (or do a dir() on it).

durin42
Oh, this is awesome. I knew, mercurial must have some cool and straightforward way of doing this, but just didn't knew it would be that easy. Thanks a lot :-)
gruszczy
Ok, this is not as good, as I would like. What about http repos? I can't iterate over them or use len.
gruszczy
You have to do this on local repo objects. That's just the way hg works, really. Why can't you just do an hg {clone,pull} and then use that as a local cache of the remote changes?
durin42
I am writing a program, that retrieves logs from a hg repository. Cloning it first seems like an overkill. If I can retrieve logs from a subversion repo without making a working copy, such thing should be possible for hg too. Forcing user to download whole repo just to retrieve logs isn't a thing, a I would like to do.
gruszczy
OK, it seems, that there is not simple way to retrieve logs from remote repo.
gruszczy
+2  A: 

yeah I had the same problem.. seems as its as designed to disallow retrieving logs remotely. The web interface gives a little rss feed but it wasn't enough of a history for me. So we created our own customised rss feed...

its not the most elaborate of things and is customised to our liking, you can mix the fields around in print_item() to change the look of the feed. You could also mod it to return log info on specific changesets if needed.

You will have to add a script alias to apache, something like (See http://httpd.apache.org/docs/2.0/howto/cgi.html for more info):

ScriptAlias /feed.cgi /usr/local/systems/hg/script/feed.cgi

feed.cgi file contents:

#!/usr/bin/env python2.5
# -*- python -*-

"""
Creates a rss feed from commit log messages in a repository/branch.
Can be filtered on commit logs from a set date eg date=2009-12-12
or by a number of days previous eg. days=7

Usage: 
 * retrieve all logs: http://hg.server/feed.cgi?repository=MyRepo
 * retrieve logs from set date: http://hg.server/feed.cgi?repository=DMyRepo&date=2009-11-11
 * retrieve logs from last 77 days: http://hg.server/feed.cgi?repository=DMyRepo&days=77
 * retrieve all logs from a branch: http://hg.server/feed.cgi?repository=MyRepo&branch=myBranch

Script Location on server: /usr/local/systems/hg/script/feed.cgi
"""

defaultdateformats = (
'%Y-%m-%d %H:%M:%S',
'%Y-%m-%d %I:%M:%S%p',
'%Y-%m-%d %H:%M',
'%Y-%m-%d %I:%M%p',
'%Y-%m-%d',
'%m-%d',
'%m/%d',
'%m/%d/%y',
'%m/%d/%Y',
'%a %b %d %H:%M:%S %Y',
'%a %b %d %I:%M:%S%p %Y',
'%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
'%b %d %H:%M:%S %Y',
'%b %d %I:%M:%S%p %Y',
'%b %d %H:%M:%S',
'%b %d %I:%M:%S%p',
'%b %d %H:%M',
'%b %d %I:%M%p',
'%b %d %Y',
'%b %d',
'%H:%M:%S',
'%I:%M:%S%p',
'%H:%M',
'%I:%M%p',
)

import os, sys, cgi, cgitb, datetime, time
cgitb.enable()

from mercurial import ui, hg, util
from mercurial.node import short

def find_repository(name):
    base = '/usr/local/systems/hg/repos/'
    path = os.path.join(base, name)

    repos = hg.repository(None, path)
    return repos

def find_changes(repos, branch, date):

    # returns true if d2 is newer than d1
    def newerDate(d1, d2):
         d1 = datetime.datetime.fromtimestamp(d1)
         d2 = datetime.datetime.fromtimestamp(d2)
         return d1 < d2

    #for ctx in repos.changelog:
    #    print ctx

    changes = repos.changelog

    out = []
    # filter on branch
    if branch != '':
        changes = [change for change in changes if repos.changectx(change).branch() == branch ]

    # filter on date
    if date != '':
        changes = [change for change in changes if newerDate(date, repos.changectx(change).date()[0]) ]

    return changes

def print_item(change, link_template):
    def _element(name, content):
        content = cgi.escape(content)

        print "      <%(name)s>%(content)s</%(name)s>" % {
            'name': name,
            'content': content
            }

    link = link_template % {'node': short(change.node())}
    print "    <item>"
    _element('title', str(change.rev()))
    _element('description', change.description())
    _element('guid', str(change.rev()))
    _element('author', change.user())
    _element('link', link)
    _element('pubdate', str(datetime.datetime.fromtimestamp(change.date()[0])))
    print "    </item>"

def print_rss(changes, repos, template):
    print """<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <link>N/A</link>
    <language>en-us</language>

    <title>Changelog</title>
    <description>Changelog</description>
"""
    for change in changes:
        ctx = repos.changectx(change)
        print_item(ctx, template)

    print """
  </channel>
</rss>
"""

if __name__=="__main__":

    # -*- python -*-
    print "Content-Type: application/rss+xml; charset=UTF-8"
    print

    f = cgi.FieldStorage()

    if not f.has_key("repository"):
        print "Need to specify repository."
        sys.exit()

    repository = f['repository'].value
    branch = ''
    if f.has_key('branch'):
        branch = f['branch'].value

    date = ''
    if f.has_key('date') and not f.has_key('days'):
        try:
            #date = datetime.datetime.strptime(f['date'].value, '%Y-%m-%d')
            date = util.parsedate(f['date'].value)[0]
        except:
            print 'Error in date format, use one of the following formats:', defaultdateformats
            sys.exit()
    elif f.has_key('days') and not f.has_key('date'):
        days = int(f['days'].value)
        try:
            date = datetime.datetime.now() - datetime.timedelta(days=days)
            date = time.mktime(date.timetuple())
        except:
            print 'Error in days, please use a standard number eg. days=7'
            sys.exit()
    elif f.has_key('days') and f.has_key('date'):
        print 'Error, please only supply a dayrange OR a date, not both'
        sys.exit()

    repos = find_repository(repository)
    changes = find_changes(repos, branch, date)
    rev_link_template = 'http://hg.server/hg/%(repos)s/rev/%%(node)s' % {
        'repos': repository
        }
    print_rss(changes, repos, rev_link_template)
Best
This is nice. Thanks :-)
gruszczy