1##########################################################################
2# Copyright (c) 2009, ETH Zurich.
3# All rights reserved.
4#
5# This file is distributed under the terms in the attached LICENSE file.
6# If you do not find this file, copies can be found by writing to:
7# ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
8##########################################################################
9
10try:
11    from mercurial import hg, ui, node, error, commands
12    mercurial_module = True
13except ImportError:
14    mercurial_module = False
15
16try:
17    import git
18    git_module = True
19except ImportError:
20    git_module = False
21
22class Checkout(object):
23    '''Checkout Base class:
24    Maintain information about revision number, and directory locations
25    '''
26
27    def __init__(self, base_dir):
28        # Set parameters
29        self.base_dir = base_dir
30
31    def get_base_dir(self):
32        return self.base_dir
33
34    ''' Returns a unique commit identifier as string '''
35    def get_revision(self):
36        return '(repository information/git/hg module not available)'
37
38    ''' Return a string (or None) containing a patch file representing local changes'''
39    def get_diff(self):
40        return None
41
42    ''' Return a dict of additional info '''
43    def get_meta(self):
44        return {}
45
46
47class CheckoutHg(Checkout):
48    def __init__(self, base_dir, repo):
49        super(CheckoutHg, self).__init__(base_dir)
50        self.repo = repo
51
52    def get_revision(self):
53        # identify the parents of the working revision
54        context = self.repo[None]
55        parents = context.parents()
56        s = ', '.join(map(lambda p: node.short(p.node()), parents))
57        if context.files() or context.deleted():
58            s += ' with local changes'
59        else:
60            s += ' unmodified'
61        return s
62
63
64    def get_diff(self):
65        context = self.repo[None]
66        if not context.files() and not context.deleted():
67            return None
68
69        diffui = ui.ui()
70        diffui.pushbuffer()
71        commands.diff(diffui, self.repo, git=True)
72        return diffui.popbuffer()
73
74class CheckoutGit(Checkout):
75    def __init__(self, base_dir, repo):
76        super(CheckoutGit, self).__init__(base_dir)
77        self.repo = repo
78
79    def get_diff(self):
80        gitc = self.repo.git
81        return gitc.diff() + "\n\n" + gitc.diff(cached=True)
82
83    def get_revision(self):
84        return self.repo.head.commit.hexsha
85
86    def get_meta(self):
87        repo = self.repo
88        headc = repo.head.commit
89        ret = {}
90        shortsha = self.repo.git.rev_parse(headc.hexsha, short=7)
91        try:
92            ret["branch"] = repo.active_branch.name
93        except TypeError, e:
94            ret["branch"] = "(HEAD detached at %s)" % shortsha
95        ret["shortrev"] = shortsha
96        ret["commitmsg"] = headc.message.split("\n")[0]
97        ret["commitmsg-tail"] = "".join(headc.message.split("\n")[1:])
98        ret["dirty"] = str(self.repo.is_dirty())
99        ret["untracked-files"] = ",".join(self.repo.untracked_files)
100        return ret
101
102
103
104''' Factory method that checks a directory and instantiates
105the correct Checkout class '''
106def create_for_dir(base_dir):
107    if git_module:
108        try:
109            return CheckoutGit(base_dir, git.Repo(base_dir))
110        except git.InvalidGitRepositoryError:
111            pass
112
113    if mercurial_module:
114        try:
115            return CheckoutHg(base_dir, hg.repository(ui.ui(), base_dir))
116        except error.RepoError:
117            pass
118
119    return Checkout(base_dir)
120
121
122
123