1"""
2Support for the Mercurial SCM
3"""
4
5
6import logging
7
8import salt.utils.data
9import salt.utils.path
10from salt.exceptions import CommandExecutionError
11
12log = logging.getLogger(__name__)
13
14
15def __virtual__():
16    """
17    Only load if hg is installed
18    """
19    if salt.utils.path.which("hg") is None:
20        return (False, "The hg execution module cannot be loaded: hg unavailable.")
21    else:
22        return True
23
24
25def _ssh_flag(identity_path):
26    return ["--ssh", "ssh -i {}".format(identity_path)]
27
28
29def revision(cwd, rev="tip", short=False, user=None):
30    """
31    Returns the long hash of a given identifier (hash, branch, tag, HEAD, etc)
32
33    cwd
34        The path to the Mercurial repository
35
36    rev: tip
37        The revision
38
39    short: False
40        Return an abbreviated commit hash
41
42    user : None
43        Run hg as a user other than what the minion runs as
44
45    CLI Example:
46
47    .. code-block:: bash
48
49        salt '*' hg.revision /path/to/repo mybranch
50    """
51    cmd = ["hg", "id", "-i", "--debug" if not short else "", "-r", "{}".format(rev)]
52
53    result = __salt__["cmd.run_all"](cmd, cwd=cwd, runas=user, python_shell=False)
54
55    if result["retcode"] == 0:
56        return result["stdout"]
57    else:
58        return ""
59
60
61def describe(cwd, rev="tip", user=None):
62    """
63    Mimic git describe and return an identifier for the given revision
64
65    cwd
66        The path to the Mercurial repository
67
68    rev: tip
69        The path to the archive tarball
70
71    user : None
72        Run hg as a user other than what the minion runs as
73
74    CLI Example:
75
76    .. code-block:: bash
77
78        salt '*' hg.describe /path/to/repo
79    """
80    cmd = [
81        "hg",
82        "log",
83        "-r",
84        "{}".format(rev),
85        "--template",
86        "'{{latesttag}}-{{latesttagdistance}}-{{node|short}}'",
87    ]
88    desc = __salt__["cmd.run_stdout"](cmd, cwd=cwd, runas=user, python_shell=False)
89
90    return desc or revision(cwd, rev, short=True)
91
92
93def archive(cwd, output, rev="tip", fmt=None, prefix=None, user=None):
94    """
95    Export a tarball from the repository
96
97    cwd
98        The path to the Mercurial repository
99
100    output
101        The path to the archive tarball
102
103    rev: tip
104        The revision to create an archive from
105
106    fmt: None
107        Format of the resulting archive. Mercurial supports: tar,
108        tbz2, tgz, zip, uzip, and files formats.
109
110    prefix : None
111        Prepend <prefix>/ to every filename in the archive
112
113    user : None
114        Run hg as a user other than what the minion runs as
115
116    If ``prefix`` is not specified it defaults to the basename of the repo
117    directory.
118
119    CLI Example:
120
121    .. code-block:: bash
122
123        salt '*' hg.archive /path/to/repo output=/tmp/archive.tgz fmt=tgz
124    """
125    cmd = [
126        "hg",
127        "archive",
128        "{}".format(output),
129        "--rev",
130        "{}".format(rev),
131    ]
132    if fmt:
133        cmd.append("--type")
134        cmd.append("{}".format(fmt))
135    if prefix:
136        cmd.append("--prefix")
137        cmd.append('"{}"'.format(prefix))
138    return __salt__["cmd.run"](cmd, cwd=cwd, runas=user, python_shell=False)
139
140
141def pull(cwd, opts=None, user=None, identity=None, repository=None):
142    """
143    Perform a pull on the given repository
144
145    cwd
146        The path to the Mercurial repository
147
148    repository : None
149        Perform pull from the repository different from .hg/hgrc:[paths]:default
150
151    opts : None
152        Any additional options to add to the command line
153
154    user : None
155        Run hg as a user other than what the minion runs as
156
157    identity : None
158        Private SSH key on the minion server for authentication (ssh://)
159
160        .. versionadded:: 2015.5.0
161
162    CLI Example:
163
164    .. code-block:: bash
165
166        salt '*' hg.pull /path/to/repo opts=-u
167    """
168    cmd = ["hg", "pull"]
169    if identity:
170        cmd.extend(_ssh_flag(identity))
171    if opts:
172        for opt in opts.split():
173            cmd.append(opt)
174    if repository is not None:
175        cmd.append(repository)
176
177    ret = __salt__["cmd.run_all"](cmd, cwd=cwd, runas=user, python_shell=False)
178    if ret["retcode"] != 0:
179        raise CommandExecutionError(
180            "Hg command failed: {}".format(ret.get("stderr", ret["stdout"]))
181        )
182
183    return ret["stdout"]
184
185
186def update(cwd, rev, force=False, user=None):
187    """
188    Update to a given revision
189
190    cwd
191        The path to the Mercurial repository
192
193    rev
194        The revision to update to
195
196    force : False
197        Force an update
198
199    user : None
200        Run hg as a user other than what the minion runs as
201
202    CLI Example:
203
204    .. code-block:: bash
205
206        salt devserver1 hg.update /path/to/repo somebranch
207    """
208    cmd = ["hg", "update", "{}".format(rev)]
209    if force:
210        cmd.append("-C")
211
212    ret = __salt__["cmd.run_all"](cmd, cwd=cwd, runas=user, python_shell=False)
213    if ret["retcode"] != 0:
214        raise CommandExecutionError(
215            "Hg command failed: {}".format(ret.get("stderr", ret["stdout"]))
216        )
217
218    return ret["stdout"]
219
220
221def clone(cwd, repository, opts=None, user=None, identity=None):
222    """
223    Clone a new repository
224
225    cwd
226        The path to the Mercurial repository
227
228    repository
229        The hg URI of the repository
230
231    opts : None
232        Any additional options to add to the command line
233
234    user : None
235        Run hg as a user other than what the minion runs as
236
237    identity : None
238        Private SSH key on the minion server for authentication (ssh://)
239
240        .. versionadded:: 2015.5.0
241
242    CLI Example:
243
244    .. code-block:: bash
245
246        salt '*' hg.clone /path/to/repo https://bitbucket.org/birkenfeld/sphinx
247    """
248    cmd = ["hg", "clone", "{}".format(repository), "{}".format(cwd)]
249    if opts:
250        for opt in opts.split():
251            cmd.append("{}".format(opt))
252    if identity:
253        cmd.extend(_ssh_flag(identity))
254
255    ret = __salt__["cmd.run_all"](cmd, runas=user, python_shell=False)
256    if ret["retcode"] != 0:
257        raise CommandExecutionError(
258            "Hg command failed: {}".format(ret.get("stderr", ret["stdout"]))
259        )
260
261    return ret["stdout"]
262
263
264def status(cwd, opts=None, user=None):
265    """
266    Show changed files of the given repository
267
268    cwd
269        The path to the Mercurial repository
270
271    opts : None
272        Any additional options to add to the command line
273
274    user : None
275        Run hg as a user other than what the minion runs as
276
277    CLI Example:
278
279    .. code-block:: bash
280
281        salt '*' hg.status /path/to/repo
282    """
283
284    def _status(cwd):
285        cmd = ["hg", "status"]
286        if opts:
287            for opt in opts.split():
288                cmd.append("{}".format(opt))
289        out = __salt__["cmd.run_stdout"](cmd, cwd=cwd, runas=user, python_shell=False)
290        types = {
291            "M": "modified",
292            "A": "added",
293            "R": "removed",
294            "C": "clean",
295            "!": "missing",
296            "?": "not tracked",
297            "I": "ignored",
298            " ": "origin of the previous file",
299        }
300        ret = {}
301        for line in out.splitlines():
302            t, f = types[line[0]], line[2:]
303            if t not in ret:
304                ret[t] = []
305            ret[t].append(f)
306        return ret
307
308    if salt.utils.data.is_iter(cwd):
309        return {cwd: _status(cwd) for cwd in cwd}
310    else:
311        return _status(cwd)
312