#!/usr/bin/env python
"""
A Mercurial extension that allows you to locally track which revisions of a
repository have been pushed to a remote repository.

When you use Mercurial in a completely disconnected fashion, at some
point you may want to be able to find out if you have new revisions
that need to be pushed out to a central repository, without having to
connect it (this is the key: you can be off the network and it works).
This command allows you to do that. It relies on an the installation
of an 'outgoing' hook which stamps all revisions everytime you push.
The record is kept under the '<root>/.hg/pushed.history' file

Usage
-----

To list the revisions that have never been pushed, just run the command::

   hg histpush

To force marking all the revisions in a repository as having been pushed, run
with the --mark option::

   hg histpush --mark

You should do this once on an existing repository after installing the histpush
extension, to initialize the history file.

Installation
------------

Add the following configuration variables to your hgrc file (or the
repository's .hg/hgrc)::

  [extensions]
  histpush = 

  [hooks]
  outgoing = python:histpush.update

Notes
-----

I use this in the context of working from my laptop: all my work files are being
'backed up' to a server when I push my changes. When I walk around town with my
laptop with unsaved work I get a little nervous about losing it... there is
nothing that angers me more than losing data. So... I want to be able to know if
I need to push changes to the main copies on my server.... if I don't, I can
safely be careless about the laptop:  you can steal it and I don't really care,
I've got all the data (I care a lot more about wasted time than about the laptop
hardware). Weird, but... true story.

Alternative
-----------

An alternative is that you could just have a setup like::

  my/project/upstream
  my/project/dev

where upstream is a pristine clone of the upstream repo and dev is `hg clone
upstream dev`. Then a simple `hg out` in dev will tell you what would get
pushed. This also makes it easy to clone another copy for a separate line of
changes (-- P. Arrenbrecht).
"""

__author__ = 'Martin Blais <blais@furius.ca>'

# stdlib imports
import os
from itertools import imap
from os.path import *

# mercurial imports
from mercurial import cmdutil
from mercurial.node import hex
from mercurial.commands import dryrunopts, templateopts
from mercurial.i18n import _


pushfn = 'pushed.history'

def histpush(ui, repo, *pats, **opts):
    """
    List the revisions that have NOT been pushed (or invoke the mark command).
    """
    histfn = join(repo.path, pushfn)

    if opts['mark']:
        mark_all(ui, repo)

    else:
        # List the revisions that are not in the push history.
        displayer = cmdutil.show_changeset(ui, repo, opts)

        f = open(histfn, 'r')
        pushed_revs = frozenset(imap(str.strip, f.xreadlines()))
        f.close()

        begin, end = cmdutil.revrange(repo, ['0', 'tip'])
        for rev in xrange(begin, end):
            node = repo.lookup(rev)
            hnode = hex(node)
            if hnode not in pushed_revs:
                displayer.show(rev, node)
            
def mark_all(ui, repo, **kwds):
    """
    Mark all the available revisions as pushed by recording the nodes in the
    history file (we just overwrite it here).
    """
    histfn = join(repo.path, pushfn)
    f = open(histfn, 'w')
    x = cmdutil.revrange(repo, ['0', 'tip'])
    begin, end = x
    for rev in xrange(begin, end):
        node = repo.lookup(rev)
        # ui.write("histpush marked: %s:%s\n" % (rev, hex(node)))
        f.write('%s\n' % hex(node))
    f.close()

update = mark_all

## FIXME: This does not work yet. For now we mark all the revs every time.
def _update(ui, repo, **kwargs):
    """
    Mark all the available revisions as pushed by recording the nodes in the
    history file (we just overwrite it here).
    """
    histfn = join(repo.path, pushfn)
    f = open(histfn, 'a')

    node_first = kwargs['node']
    childstack = [repo.changectx(node_first)]
    while childstack:
        ctx = childstack.pop()
        ui.write("histpush marked: %s:%s\n" % (ctx.rev(), hex(ctx.node())))
        f.write('%s\n' % hex(ctx.node()))
        childstack.extend(ctx.children())
    f.close()


histpushopts = [
    ('m', 'mark', None, _('mark all current revisions as pushed')),
]

cmdtable = {
    "histpush": (histpush, dryrunopts + histpushopts + templateopts,
                "hg histpush [options]")
}

