Show More
@@ -1,130 +1,131 b'' | |||
|
1 | 1 | # perf.py - performance test routines |
|
2 | '''helper extension to measure performance''' | |
|
2 | 3 | |
|
3 | 4 | from mercurial import cmdutil, match, commands |
|
4 | 5 | import time, os, sys |
|
5 | 6 | |
|
6 | 7 | def timer(func): |
|
7 | 8 | results = [] |
|
8 | 9 | begin = time.time() |
|
9 | 10 | count = 0 |
|
10 | 11 | while 1: |
|
11 | 12 | ostart = os.times() |
|
12 | 13 | cstart = time.time() |
|
13 | 14 | r = func() |
|
14 | 15 | cstop = time.time() |
|
15 | 16 | ostop = os.times() |
|
16 | 17 | count += 1 |
|
17 | 18 | a, b = ostart, ostop |
|
18 | 19 | results.append((cstop - cstart, b[0] - a[0], b[1]-a[1])) |
|
19 | 20 | if cstop - begin > 3 and count >= 100: |
|
20 | 21 | break |
|
21 | 22 | if cstop - begin > 10 and count >= 3: |
|
22 | 23 | break |
|
23 | 24 | if r: |
|
24 | 25 | sys.stderr.write("! result: %s\n" % r) |
|
25 | 26 | m = min(results) |
|
26 | 27 | sys.stderr.write("! wall %f comb %f user %f sys %f (best of %d)\n" |
|
27 | 28 | % (m[0], m[1] + m[2], m[1], m[2], count)) |
|
28 | 29 | |
|
29 | 30 | def perfwalk(ui, repo, *pats): |
|
30 | 31 | try: |
|
31 | 32 | m = cmdutil.match(repo, pats, {}) |
|
32 | 33 | timer(lambda: len(list(repo.dirstate.walk(m, True, False)))) |
|
33 | 34 | except: |
|
34 | 35 | try: |
|
35 | 36 | m = cmdutil.match(repo, pats, {}) |
|
36 | 37 | timer(lambda: len([b for a,b,c in repo.dirstate.statwalk([], m)])) |
|
37 | 38 | except: |
|
38 | 39 | timer(lambda: len(list(cmdutil.walk(repo, pats, {})))) |
|
39 | 40 | |
|
40 | 41 | def perfstatus(ui, repo, *pats): |
|
41 | 42 | #m = match.always(repo.root, repo.getcwd()) |
|
42 | 43 | #timer(lambda: sum(map(len, repo.dirstate.status(m, False, False, False)))) |
|
43 | 44 | timer(lambda: sum(map(len, repo.status()))) |
|
44 | 45 | |
|
45 | 46 | def perfheads(ui, repo): |
|
46 | 47 | timer(lambda: len(repo.changelog.heads())) |
|
47 | 48 | |
|
48 | 49 | def perftags(ui, repo): |
|
49 | 50 | import mercurial.changelog, mercurial.manifest |
|
50 | 51 | def t(): |
|
51 | 52 | repo.changelog = mercurial.changelog.changelog(repo.sopener) |
|
52 | 53 | repo.manifest = mercurial.manifest.manifest(repo.sopener) |
|
53 | 54 | repo.tagscache = None |
|
54 | 55 | return len(repo.tags()) |
|
55 | 56 | timer(t) |
|
56 | 57 | |
|
57 | 58 | def perfdirstate(ui, repo): |
|
58 | 59 | "a" in repo.dirstate |
|
59 | 60 | def d(): |
|
60 | 61 | repo.dirstate.invalidate() |
|
61 | 62 | "a" in repo.dirstate |
|
62 | 63 | timer(d) |
|
63 | 64 | |
|
64 | 65 | def perfdirstatedirs(ui, repo): |
|
65 | 66 | "a" in repo.dirstate |
|
66 | 67 | def d(): |
|
67 | 68 | "a" in repo.dirstate._dirs |
|
68 | 69 | del repo.dirstate._dirs |
|
69 | 70 | timer(d) |
|
70 | 71 | |
|
71 | 72 | def perfmanifest(ui, repo): |
|
72 | 73 | def d(): |
|
73 | 74 | t = repo.manifest.tip() |
|
74 | 75 | m = repo.manifest.read(t) |
|
75 | 76 | repo.manifest.mapcache = None |
|
76 | 77 | repo.manifest._cache = None |
|
77 | 78 | timer(d) |
|
78 | 79 | |
|
79 | 80 | def perfindex(ui, repo): |
|
80 | 81 | import mercurial.changelog |
|
81 | 82 | def d(): |
|
82 | 83 | t = repo.changelog.tip() |
|
83 | 84 | repo.changelog = mercurial.changelog.changelog(repo.sopener) |
|
84 | 85 | repo.changelog._loadindexmap() |
|
85 | 86 | timer(d) |
|
86 | 87 | |
|
87 | 88 | def perfstartup(ui, repo): |
|
88 | 89 | cmd = sys.argv[0] |
|
89 | 90 | def d(): |
|
90 | 91 | os.system("HGRCPATH= %s version -q > /dev/null" % cmd) |
|
91 | 92 | timer(d) |
|
92 | 93 | |
|
93 | 94 | def perfparents(ui, repo): |
|
94 | 95 | nl = [repo.changelog.node(i) for i in xrange(1000)] |
|
95 | 96 | def d(): |
|
96 | 97 | for n in nl: |
|
97 | 98 | repo.changelog.parents(n) |
|
98 | 99 | timer(d) |
|
99 | 100 | |
|
100 | 101 | def perflookup(ui, repo, rev): |
|
101 | 102 | timer(lambda: len(repo.lookup(rev))) |
|
102 | 103 | |
|
103 | 104 | def perflog(ui, repo): |
|
104 | 105 | ui.pushbuffer() |
|
105 | 106 | timer(lambda: commands.log(ui, repo, rev=[], date='', user='')) |
|
106 | 107 | ui.popbuffer() |
|
107 | 108 | |
|
108 | 109 | def perftemplating(ui, repo): |
|
109 | 110 | ui.pushbuffer() |
|
110 | 111 | timer(lambda: commands.log(ui, repo, rev=[], date='', user='', |
|
111 | 112 | template='{date|shortdate} [{rev}:{node|short}]' |
|
112 | 113 | ' {author|person}: {desc|firstline}\n')) |
|
113 | 114 | ui.popbuffer() |
|
114 | 115 | |
|
115 | 116 | cmdtable = { |
|
116 | 117 | 'perflookup': (perflookup, []), |
|
117 | 118 | 'perfparents': (perfparents, []), |
|
118 | 119 | 'perfstartup': (perfstartup, []), |
|
119 | 120 | 'perfstatus': (perfstatus, []), |
|
120 | 121 | 'perfwalk': (perfwalk, []), |
|
121 | 122 | 'perfmanifest': (perfmanifest, []), |
|
122 | 123 | 'perfindex': (perfindex, []), |
|
123 | 124 | 'perfheads': (perfheads, []), |
|
124 | 125 | 'perftags': (perftags, []), |
|
125 | 126 | 'perfdirstate': (perfdirstate, []), |
|
126 | 127 | 'perfdirstatedirs': (perfdirstate, []), |
|
127 | 128 | 'perflog': (perflog, []), |
|
128 | 129 | 'perftemplating': (perftemplating, []), |
|
129 | 130 | } |
|
130 | 131 |
@@ -1,99 +1,99 b'' | |||
|
1 | 1 | # acl.py - changeset access control for mercurial |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2, incorporated herein by reference. |
|
7 | 7 | # |
|
8 | # this hook allows to allow or deny access to parts of a repo when | |
|
9 | # taking incoming changesets. | |
|
10 | # | |
|
11 |
|
|
|
12 |
|
|
|
13 | # spoof). | |
|
14 | # | |
|
15 | # acl hook is best to use if you use hgsh to set up restricted shells | |
|
16 | # for authenticated users to only push to / pull from. not safe if | |
|
17 | # user has interactive shell access, because they can disable hook. | |
|
18 | # also not safe if remote users share one local account, because then | |
|
19 | # no way to tell remote users apart. | |
|
20 | # | |
|
21 | # to use, configure acl extension in hgrc like this: | |
|
22 | # | |
|
23 | # [extensions] | |
|
24 | # hgext.acl = | |
|
25 | # | |
|
26 | # [hooks] | |
|
27 | # pretxnchangegroup.acl = python:hgext.acl.hook | |
|
28 | # | |
|
29 | # [acl] | |
|
30 | # sources = serve # check if source of incoming changes in this list | |
|
31 | # # ("serve" == ssh or http, "push", "pull", "bundle") | |
|
32 | # | |
|
33 | # allow and deny lists have subtree pattern (default syntax is glob) | |
|
34 | # on left, user names on right. deny list checked before allow list. | |
|
35 | # | |
|
36 | # [acl.allow] | |
|
37 |
|
|
|
38 | # # empty acl.allow = no users allowed | |
|
39 | # docs/** = doc_writer | |
|
40 | # .hgtags = release_engineer | |
|
41 | # | |
|
42 | # [acl.deny] | |
|
43 | # # if acl.deny not present, no users denied by default | |
|
44 | # # empty acl.deny = all users allowed | |
|
45 | # glob pattern = user4, user5 | |
|
46 | # ** = user6 | |
|
8 | ||
|
9 | '''provide simple hooks for access control | |
|
10 | ||
|
11 | Authorization is against local user name on system where hook is run, not | |
|
12 | committer of original changeset (since that is easy to spoof). | |
|
13 | ||
|
14 | The acl hook is best to use if you use hgsh to set up restricted shells for | |
|
15 | authenticated users to only push to / pull from. It's not safe if user has | |
|
16 | interactive shell access, because they can disable the hook. It's also not | |
|
17 | safe if remote users share one local account, because then there's no way to | |
|
18 | tell remote users apart. | |
|
19 | ||
|
20 | To use, configure the acl extension in hgrc like this: | |
|
21 | ||
|
22 | [extensions] | |
|
23 | hgext.acl = | |
|
24 | ||
|
25 | [hooks] | |
|
26 | pretxnchangegroup.acl = python:hgext.acl.hook | |
|
27 | ||
|
28 | [acl] | |
|
29 | sources = serve # check if source of incoming changes in this list | |
|
30 | # ("serve" == ssh or http, "push", "pull", "bundle") | |
|
31 | ||
|
32 | Allow and deny lists have a subtree pattern (default syntax is glob) on the | |
|
33 | left and user names on right. The deny list is checked before the allow list. | |
|
34 | ||
|
35 | [acl.allow] | |
|
36 | # if acl.allow not present, all users allowed by default | |
|
37 | # empty acl.allow = no users allowed | |
|
38 | docs/** = doc_writer | |
|
39 | .hgtags = release_engineer | |
|
40 | ||
|
41 | [acl.deny] | |
|
42 | # if acl.deny not present, no users denied by default | |
|
43 | # empty acl.deny = all users allowed | |
|
44 | glob pattern = user4, user5 | |
|
45 | ** = user6 | |
|
46 | ''' | |
|
47 | 47 | |
|
48 | 48 | from mercurial.i18n import _ |
|
49 | 49 | from mercurial import util, match |
|
50 | 50 | import getpass, urllib |
|
51 | 51 | |
|
52 | 52 | def buildmatch(ui, repo, user, key): |
|
53 | 53 | '''return tuple of (match function, list enabled).''' |
|
54 | 54 | if not ui.has_section(key): |
|
55 | 55 | ui.debug(_('acl: %s not enabled\n') % key) |
|
56 | 56 | return None |
|
57 | 57 | |
|
58 | 58 | pats = [pat for pat, users in ui.configitems(key) |
|
59 | 59 | if user in users.replace(',', ' ').split()] |
|
60 | 60 | ui.debug(_('acl: %s enabled, %d entries for user %s\n') % |
|
61 | 61 | (key, len(pats), user)) |
|
62 | 62 | if pats: |
|
63 | 63 | return match.match(repo.root, '', pats) |
|
64 | 64 | return match.exact(repo.root, '', []) |
|
65 | 65 | |
|
66 | 66 | |
|
67 | 67 | def hook(ui, repo, hooktype, node=None, source=None, **kwargs): |
|
68 | 68 | if hooktype != 'pretxnchangegroup': |
|
69 | 69 | raise util.Abort(_('config error - hook type "%s" cannot stop ' |
|
70 | 70 | 'incoming changesets') % hooktype) |
|
71 | 71 | if source not in ui.config('acl', 'sources', 'serve').split(): |
|
72 | 72 | ui.debug(_('acl: changes have source "%s" - skipping\n') % source) |
|
73 | 73 | return |
|
74 | 74 | |
|
75 | 75 | user = None |
|
76 | 76 | if source == 'serve' and 'url' in kwargs: |
|
77 | 77 | url = kwargs['url'].split(':') |
|
78 | 78 | if url[0] == 'remote' and url[1].startswith('http'): |
|
79 | 79 | user = urllib.unquote(url[2]) |
|
80 | 80 | |
|
81 | 81 | if user is None: |
|
82 | 82 | user = getpass.getuser() |
|
83 | 83 | |
|
84 | 84 | cfg = ui.config('acl', 'config') |
|
85 | 85 | if cfg: |
|
86 | 86 | ui.readconfig(cfg, sections = ['acl.allow', 'acl.deny']) |
|
87 | 87 | allow = buildmatch(ui, repo, user, 'acl.allow') |
|
88 | 88 | deny = buildmatch(ui, repo, user, 'acl.deny') |
|
89 | 89 | |
|
90 | 90 | for rev in xrange(repo[node], len(repo)): |
|
91 | 91 | ctx = repo[rev] |
|
92 | 92 | for f in ctx.files(): |
|
93 | 93 | if deny and deny(f): |
|
94 | 94 | ui.debug(_('acl: user %s denied on %s\n') % (user, f)) |
|
95 | 95 | raise util.Abort(_('acl: access denied for changeset %s') % ctx) |
|
96 | 96 | if allow and not allow(f): |
|
97 | 97 | ui.debug(_('acl: user %s not allowed on %s\n') % (user, f)) |
|
98 | 98 | raise util.Abort(_('acl: access denied for changeset %s') % ctx) |
|
99 | 99 | ui.debug(_('acl: allowing changeset %s\n') % ctx) |
@@ -1,42 +1,44 b'' | |||
|
1 | 1 | # Mercurial extension to provide the 'hg children' command |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2007 by Intevation GmbH <intevation@intevation.de> |
|
4 | 4 | # |
|
5 | 5 | # Author(s): |
|
6 | 6 | # Thomas Arendsen Hein <thomas@intevation.de> |
|
7 | 7 | # |
|
8 | 8 | # This software may be used and distributed according to the terms of the |
|
9 | 9 | # GNU General Public License version 2, incorporated herein by reference. |
|
10 | 10 | |
|
11 | '''provides children command to show children changesets''' | |
|
12 | ||
|
11 | 13 | from mercurial import cmdutil |
|
12 | 14 | from mercurial.commands import templateopts |
|
13 | 15 | from mercurial.i18n import _ |
|
14 | 16 | |
|
15 | 17 | |
|
16 | 18 | def children(ui, repo, file_=None, **opts): |
|
17 | 19 | """show the children of the given or working directory revision |
|
18 | 20 | |
|
19 | 21 | Print the children of the working directory's revisions. If a |
|
20 | 22 | revision is given via -r/--rev, the children of that revision will |
|
21 | 23 | be printed. If a file argument is given, revision in which the |
|
22 | 24 | file was last changed (after the working directory revision or the |
|
23 | 25 | argument to --rev if given) is printed. |
|
24 | 26 | """ |
|
25 | 27 | rev = opts.get('rev') |
|
26 | 28 | if file_: |
|
27 | 29 | ctx = repo.filectx(file_, changeid=rev) |
|
28 | 30 | else: |
|
29 | 31 | ctx = repo[rev] |
|
30 | 32 | |
|
31 | 33 | displayer = cmdutil.show_changeset(ui, repo, opts) |
|
32 | 34 | for cctx in ctx.children(): |
|
33 | 35 | displayer.show(cctx) |
|
34 | 36 | |
|
35 | 37 | |
|
36 | 38 | cmdtable = { |
|
37 | 39 | "children": |
|
38 | 40 | (children, |
|
39 | 41 | [('r', 'rev', '', _('show children of the specified revision')), |
|
40 | 42 | ] + templateopts, |
|
41 | 43 | _('hg children [-r REV] [FILE]')), |
|
42 | 44 | } |
@@ -1,228 +1,229 b'' | |||
|
1 | 1 | # extdiff.py - external diff program support for mercurial |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2, incorporated herein by reference. |
|
7 | 7 | |
|
8 | ''' | |
|
8 | '''allow external programs to compare revisions | |
|
9 | ||
|
9 | 10 | The `extdiff' Mercurial extension allows you to use external programs |
|
10 | 11 | to compare revisions, or revision with working directory. The external diff |
|
11 | 12 | programs are called with a configurable set of options and two |
|
12 | 13 | non-option arguments: paths to directories containing snapshots of |
|
13 | 14 | files to compare. |
|
14 | 15 | |
|
15 | 16 | The `extdiff' extension also allows to configure new diff commands, so |
|
16 | 17 | you do not need to type "hg extdiff -p kdiff3" always. |
|
17 | 18 | |
|
18 | 19 | [extdiff] |
|
19 | 20 | # add new command that runs GNU diff(1) in 'context diff' mode |
|
20 | 21 | cdiff = gdiff -Nprc5 |
|
21 | 22 | ## or the old way: |
|
22 | 23 | #cmd.cdiff = gdiff |
|
23 | 24 | #opts.cdiff = -Nprc5 |
|
24 | 25 | |
|
25 | 26 | # add new command called vdiff, runs kdiff3 |
|
26 | 27 | vdiff = kdiff3 |
|
27 | 28 | |
|
28 | 29 | # add new command called meld, runs meld (no need to name twice) |
|
29 | 30 | meld = |
|
30 | 31 | |
|
31 | 32 | # add new command called vimdiff, runs gvimdiff with DirDiff plugin |
|
32 | 33 | # (see http://www.vim.org/scripts/script.php?script_id=102) |
|
33 | 34 | # Non English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in |
|
34 | 35 | # your .vimrc |
|
35 | 36 | vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)' |
|
36 | 37 | |
|
37 | 38 | You can use -I/-X and list of file or directory names like normal "hg |
|
38 | 39 | diff" command. The `extdiff' extension makes snapshots of only needed |
|
39 | 40 | files, so running the external diff program will actually be pretty |
|
40 | 41 | fast (at least faster than having to compare the entire tree). |
|
41 | 42 | ''' |
|
42 | 43 | |
|
43 | 44 | from mercurial.i18n import _ |
|
44 | 45 | from mercurial.node import short |
|
45 | 46 | from mercurial import cmdutil, util, commands |
|
46 | 47 | import os, shlex, shutil, tempfile |
|
47 | 48 | |
|
48 | 49 | def snapshot(ui, repo, files, node, tmproot): |
|
49 | 50 | '''snapshot files as of some revision |
|
50 | 51 | if not using snapshot, -I/-X does not work and recursive diff |
|
51 | 52 | in tools like kdiff3 and meld displays too many files.''' |
|
52 | 53 | dirname = os.path.basename(repo.root) |
|
53 | 54 | if dirname == "": |
|
54 | 55 | dirname = "root" |
|
55 | 56 | if node is not None: |
|
56 | 57 | dirname = '%s.%s' % (dirname, short(node)) |
|
57 | 58 | base = os.path.join(tmproot, dirname) |
|
58 | 59 | os.mkdir(base) |
|
59 | 60 | if node is not None: |
|
60 | 61 | ui.note(_('making snapshot of %d files from rev %s\n') % |
|
61 | 62 | (len(files), short(node))) |
|
62 | 63 | else: |
|
63 | 64 | ui.note(_('making snapshot of %d files from working directory\n') % |
|
64 | 65 | (len(files))) |
|
65 | 66 | wopener = util.opener(base) |
|
66 | 67 | fns_and_mtime = [] |
|
67 | 68 | ctx = repo[node] |
|
68 | 69 | for fn in files: |
|
69 | 70 | wfn = util.pconvert(fn) |
|
70 | 71 | if not wfn in ctx: |
|
71 | 72 | # skipping new file after a merge ? |
|
72 | 73 | continue |
|
73 | 74 | ui.note(' %s\n' % wfn) |
|
74 | 75 | dest = os.path.join(base, wfn) |
|
75 | 76 | fctx = ctx[wfn] |
|
76 | 77 | data = repo.wwritedata(wfn, fctx.data()) |
|
77 | 78 | if 'l' in fctx.flags(): |
|
78 | 79 | wopener.symlink(data, wfn) |
|
79 | 80 | else: |
|
80 | 81 | wopener(wfn, 'w').write(data) |
|
81 | 82 | if 'x' in fctx.flags(): |
|
82 | 83 | util.set_flags(dest, False, True) |
|
83 | 84 | if node is None: |
|
84 | 85 | fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest))) |
|
85 | 86 | return dirname, fns_and_mtime |
|
86 | 87 | |
|
87 | 88 | def dodiff(ui, repo, diffcmd, diffopts, pats, opts): |
|
88 | 89 | '''Do the actuall diff: |
|
89 | 90 | |
|
90 | 91 | - copy to a temp structure if diffing 2 internal revisions |
|
91 | 92 | - copy to a temp structure if diffing working revision with |
|
92 | 93 | another one and more than 1 file is changed |
|
93 | 94 | - just invoke the diff for a single file in the working dir |
|
94 | 95 | ''' |
|
95 | 96 | |
|
96 | 97 | revs = opts.get('rev') |
|
97 | 98 | change = opts.get('change') |
|
98 | 99 | |
|
99 | 100 | if revs and change: |
|
100 | 101 | msg = _('cannot specify --rev and --change at the same time') |
|
101 | 102 | raise util.Abort(msg) |
|
102 | 103 | elif change: |
|
103 | 104 | node2 = repo.lookup(change) |
|
104 | 105 | node1 = repo[node2].parents()[0].node() |
|
105 | 106 | else: |
|
106 | 107 | node1, node2 = cmdutil.revpair(repo, revs) |
|
107 | 108 | |
|
108 | 109 | matcher = cmdutil.match(repo, pats, opts) |
|
109 | 110 | modified, added, removed = repo.status(node1, node2, matcher)[:3] |
|
110 | 111 | if not (modified or added or removed): |
|
111 | 112 | return 0 |
|
112 | 113 | |
|
113 | 114 | tmproot = tempfile.mkdtemp(prefix='extdiff.') |
|
114 | 115 | dir2root = '' |
|
115 | 116 | try: |
|
116 | 117 | # Always make a copy of node1 |
|
117 | 118 | dir1 = snapshot(ui, repo, modified + removed, node1, tmproot)[0] |
|
118 | 119 | changes = len(modified) + len(removed) + len(added) |
|
119 | 120 | |
|
120 | 121 | # If node2 in not the wc or there is >1 change, copy it |
|
121 | 122 | if node2 or changes > 1: |
|
122 | 123 | dir2, fns_and_mtime = snapshot(ui, repo, modified + added, node2, tmproot) |
|
123 | 124 | else: |
|
124 | 125 | # This lets the diff tool open the changed file directly |
|
125 | 126 | dir2 = '' |
|
126 | 127 | dir2root = repo.root |
|
127 | 128 | fns_and_mtime = [] |
|
128 | 129 | |
|
129 | 130 | # If only one change, diff the files instead of the directories |
|
130 | 131 | if changes == 1 : |
|
131 | 132 | if len(modified): |
|
132 | 133 | dir1 = os.path.join(dir1, util.localpath(modified[0])) |
|
133 | 134 | dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0])) |
|
134 | 135 | elif len(removed) : |
|
135 | 136 | dir1 = os.path.join(dir1, util.localpath(removed[0])) |
|
136 | 137 | dir2 = os.devnull |
|
137 | 138 | else: |
|
138 | 139 | dir1 = os.devnull |
|
139 | 140 | dir2 = os.path.join(dir2root, dir2, util.localpath(added[0])) |
|
140 | 141 | |
|
141 | 142 | cmdline = ('%s %s %s %s' % |
|
142 | 143 | (util.shellquote(diffcmd), ' '.join(diffopts), |
|
143 | 144 | util.shellquote(dir1), util.shellquote(dir2))) |
|
144 | 145 | ui.debug(_('running %r in %s\n') % (cmdline, tmproot)) |
|
145 | 146 | util.system(cmdline, cwd=tmproot) |
|
146 | 147 | |
|
147 | 148 | for copy_fn, working_fn, mtime in fns_and_mtime: |
|
148 | 149 | if os.path.getmtime(copy_fn) != mtime: |
|
149 | 150 | ui.debug(_('file changed while diffing. ' |
|
150 | 151 | 'Overwriting: %s (src: %s)\n') % (working_fn, copy_fn)) |
|
151 | 152 | util.copyfile(copy_fn, working_fn) |
|
152 | 153 | |
|
153 | 154 | return 1 |
|
154 | 155 | finally: |
|
155 | 156 | ui.note(_('cleaning up temp directory\n')) |
|
156 | 157 | shutil.rmtree(tmproot) |
|
157 | 158 | |
|
158 | 159 | def extdiff(ui, repo, *pats, **opts): |
|
159 | 160 | '''use external program to diff repository (or selected files) |
|
160 | 161 | |
|
161 | 162 | Show differences between revisions for the specified files, using |
|
162 | 163 | an external program. The default program used is diff, with |
|
163 | 164 | default options "-Npru". |
|
164 | 165 | |
|
165 | 166 | To select a different program, use the -p/--program option. The |
|
166 | 167 | program will be passed the names of two directories to compare. To |
|
167 | 168 | pass additional options to the program, use -o/--option. These |
|
168 | 169 | will be passed before the names of the directories to compare. |
|
169 | 170 | |
|
170 | 171 | When two revision arguments are given, then changes are shown |
|
171 | 172 | between those revisions. If only one revision is specified then |
|
172 | 173 | that revision is compared to the working directory, and, when no |
|
173 | 174 | revisions are specified, the working directory files are compared |
|
174 | 175 | to its parent.''' |
|
175 | 176 | program = opts['program'] or 'diff' |
|
176 | 177 | if opts['program']: |
|
177 | 178 | option = opts['option'] |
|
178 | 179 | else: |
|
179 | 180 | option = opts['option'] or ['-Npru'] |
|
180 | 181 | return dodiff(ui, repo, program, option, pats, opts) |
|
181 | 182 | |
|
182 | 183 | cmdtable = { |
|
183 | 184 | "extdiff": |
|
184 | 185 | (extdiff, |
|
185 | 186 | [('p', 'program', '', _('comparison program to run')), |
|
186 | 187 | ('o', 'option', [], _('pass option to comparison program')), |
|
187 | 188 | ('r', 'rev', [], _('revision')), |
|
188 | 189 | ('c', 'change', '', _('change made by revision')), |
|
189 | 190 | ] + commands.walkopts, |
|
190 | 191 | _('hg extdiff [OPT]... [FILE]...')), |
|
191 | 192 | } |
|
192 | 193 | |
|
193 | 194 | def uisetup(ui): |
|
194 | 195 | for cmd, path in ui.configitems('extdiff'): |
|
195 | 196 | if cmd.startswith('cmd.'): |
|
196 | 197 | cmd = cmd[4:] |
|
197 | 198 | if not path: path = cmd |
|
198 | 199 | diffopts = ui.config('extdiff', 'opts.' + cmd, '') |
|
199 | 200 | diffopts = diffopts and [diffopts] or [] |
|
200 | 201 | elif cmd.startswith('opts.'): |
|
201 | 202 | continue |
|
202 | 203 | else: |
|
203 | 204 | # command = path opts |
|
204 | 205 | if path: |
|
205 | 206 | diffopts = shlex.split(path) |
|
206 | 207 | path = diffopts.pop(0) |
|
207 | 208 | else: |
|
208 | 209 | path, diffopts = cmd, [] |
|
209 | 210 | def save(cmd, path, diffopts): |
|
210 | 211 | '''use closure to save diff command to use''' |
|
211 | 212 | def mydiff(ui, repo, *pats, **opts): |
|
212 | 213 | return dodiff(ui, repo, path, diffopts, pats, opts) |
|
213 | 214 | mydiff.__doc__ = '''use %(path)s to diff repository (or selected files) |
|
214 | 215 | |
|
215 | 216 | Show differences between revisions for the specified |
|
216 | 217 | files, using the %(path)s program. |
|
217 | 218 | |
|
218 | 219 | When two revision arguments are given, then changes are |
|
219 | 220 | shown between those revisions. If only one revision is |
|
220 | 221 | specified then that revision is compared to the working |
|
221 | 222 | directory, and, when no revisions are specified, the |
|
222 | 223 | working directory files are compared to its parent.''' % { |
|
223 | 224 | 'path': util.uirepr(path), |
|
224 | 225 | } |
|
225 | 226 | return mydiff |
|
226 | 227 | cmdtable[cmd] = (save(cmd, path, diffopts), |
|
227 | 228 | cmdtable['extdiff'][1][1:], |
|
228 | 229 | _('hg %s [OPTION]... [FILE]...') % cmd) |
@@ -1,283 +1,283 b'' | |||
|
1 | # GnuPG signing extension for Mercurial | |
|
2 | # | |
|
3 | 1 |
|
|
4 | 2 | # |
|
5 | 3 | # This software may be used and distributed according to the terms of the |
|
6 | 4 | # GNU General Public License version 2, incorporated herein by reference. |
|
7 | 5 | |
|
6 | '''GnuPG signing extension for Mercurial''' | |
|
7 | ||
|
8 | 8 | import os, tempfile, binascii |
|
9 | 9 | from mercurial import util, commands, match |
|
10 | 10 | from mercurial import node as hgnode |
|
11 | 11 | from mercurial.i18n import _ |
|
12 | 12 | |
|
13 | 13 | class gpg(object): |
|
14 | 14 | def __init__(self, path, key=None): |
|
15 | 15 | self.path = path |
|
16 | 16 | self.key = (key and " --local-user \"%s\"" % key) or "" |
|
17 | 17 | |
|
18 | 18 | def sign(self, data): |
|
19 | 19 | gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key) |
|
20 | 20 | return util.filter(data, gpgcmd) |
|
21 | 21 | |
|
22 | 22 | def verify(self, data, sig): |
|
23 | 23 | """ returns of the good and bad signatures""" |
|
24 | 24 | sigfile = datafile = None |
|
25 | 25 | try: |
|
26 | 26 | # create temporary files |
|
27 | 27 | fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig") |
|
28 | 28 | fp = os.fdopen(fd, 'wb') |
|
29 | 29 | fp.write(sig) |
|
30 | 30 | fp.close() |
|
31 | 31 | fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt") |
|
32 | 32 | fp = os.fdopen(fd, 'wb') |
|
33 | 33 | fp.write(data) |
|
34 | 34 | fp.close() |
|
35 | 35 | gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify " |
|
36 | 36 | "\"%s\" \"%s\"" % (self.path, sigfile, datafile)) |
|
37 | 37 | ret = util.filter("", gpgcmd) |
|
38 | 38 | finally: |
|
39 | 39 | for f in (sigfile, datafile): |
|
40 | 40 | try: |
|
41 | 41 | if f: os.unlink(f) |
|
42 | 42 | except: pass |
|
43 | 43 | keys = [] |
|
44 | 44 | key, fingerprint = None, None |
|
45 | 45 | err = "" |
|
46 | 46 | for l in ret.splitlines(): |
|
47 | 47 | # see DETAILS in the gnupg documentation |
|
48 | 48 | # filter the logger output |
|
49 | 49 | if not l.startswith("[GNUPG:]"): |
|
50 | 50 | continue |
|
51 | 51 | l = l[9:] |
|
52 | 52 | if l.startswith("ERRSIG"): |
|
53 | 53 | err = _("error while verifying signature") |
|
54 | 54 | break |
|
55 | 55 | elif l.startswith("VALIDSIG"): |
|
56 | 56 | # fingerprint of the primary key |
|
57 | 57 | fingerprint = l.split()[10] |
|
58 | 58 | elif (l.startswith("GOODSIG") or |
|
59 | 59 | l.startswith("EXPSIG") or |
|
60 | 60 | l.startswith("EXPKEYSIG") or |
|
61 | 61 | l.startswith("BADSIG")): |
|
62 | 62 | if key is not None: |
|
63 | 63 | keys.append(key + [fingerprint]) |
|
64 | 64 | key = l.split(" ", 2) |
|
65 | 65 | fingerprint = None |
|
66 | 66 | if err: |
|
67 | 67 | return err, [] |
|
68 | 68 | if key is not None: |
|
69 | 69 | keys.append(key + [fingerprint]) |
|
70 | 70 | return err, keys |
|
71 | 71 | |
|
72 | 72 | def newgpg(ui, **opts): |
|
73 | 73 | """create a new gpg instance""" |
|
74 | 74 | gpgpath = ui.config("gpg", "cmd", "gpg") |
|
75 | 75 | gpgkey = opts.get('key') |
|
76 | 76 | if not gpgkey: |
|
77 | 77 | gpgkey = ui.config("gpg", "key", None) |
|
78 | 78 | return gpg(gpgpath, gpgkey) |
|
79 | 79 | |
|
80 | 80 | def sigwalk(repo): |
|
81 | 81 | """ |
|
82 | 82 | walk over every sigs, yields a couple |
|
83 | 83 | ((node, version, sig), (filename, linenumber)) |
|
84 | 84 | """ |
|
85 | 85 | def parsefile(fileiter, context): |
|
86 | 86 | ln = 1 |
|
87 | 87 | for l in fileiter: |
|
88 | 88 | if not l: |
|
89 | 89 | continue |
|
90 | 90 | yield (l.split(" ", 2), (context, ln)) |
|
91 | 91 | ln +=1 |
|
92 | 92 | |
|
93 | 93 | # read the heads |
|
94 | 94 | fl = repo.file(".hgsigs") |
|
95 | 95 | for r in reversed(fl.heads()): |
|
96 | 96 | fn = ".hgsigs|%s" % hgnode.short(r) |
|
97 | 97 | for item in parsefile(fl.read(r).splitlines(), fn): |
|
98 | 98 | yield item |
|
99 | 99 | try: |
|
100 | 100 | # read local signatures |
|
101 | 101 | fn = "localsigs" |
|
102 | 102 | for item in parsefile(repo.opener(fn), fn): |
|
103 | 103 | yield item |
|
104 | 104 | except IOError: |
|
105 | 105 | pass |
|
106 | 106 | |
|
107 | 107 | def getkeys(ui, repo, mygpg, sigdata, context): |
|
108 | 108 | """get the keys who signed a data""" |
|
109 | 109 | fn, ln = context |
|
110 | 110 | node, version, sig = sigdata |
|
111 | 111 | prefix = "%s:%d" % (fn, ln) |
|
112 | 112 | node = hgnode.bin(node) |
|
113 | 113 | |
|
114 | 114 | data = node2txt(repo, node, version) |
|
115 | 115 | sig = binascii.a2b_base64(sig) |
|
116 | 116 | err, keys = mygpg.verify(data, sig) |
|
117 | 117 | if err: |
|
118 | 118 | ui.warn("%s:%d %s\n" % (fn, ln , err)) |
|
119 | 119 | return None |
|
120 | 120 | |
|
121 | 121 | validkeys = [] |
|
122 | 122 | # warn for expired key and/or sigs |
|
123 | 123 | for key in keys: |
|
124 | 124 | if key[0] == "BADSIG": |
|
125 | 125 | ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2])) |
|
126 | 126 | continue |
|
127 | 127 | if key[0] == "EXPSIG": |
|
128 | 128 | ui.write(_("%s Note: Signature has expired" |
|
129 | 129 | " (signed by: \"%s\")\n") % (prefix, key[2])) |
|
130 | 130 | elif key[0] == "EXPKEYSIG": |
|
131 | 131 | ui.write(_("%s Note: This key has expired" |
|
132 | 132 | " (signed by: \"%s\")\n") % (prefix, key[2])) |
|
133 | 133 | validkeys.append((key[1], key[2], key[3])) |
|
134 | 134 | return validkeys |
|
135 | 135 | |
|
136 | 136 | def sigs(ui, repo): |
|
137 | 137 | """list signed changesets""" |
|
138 | 138 | mygpg = newgpg(ui) |
|
139 | 139 | revs = {} |
|
140 | 140 | |
|
141 | 141 | for data, context in sigwalk(repo): |
|
142 | 142 | node, version, sig = data |
|
143 | 143 | fn, ln = context |
|
144 | 144 | try: |
|
145 | 145 | n = repo.lookup(node) |
|
146 | 146 | except KeyError: |
|
147 | 147 | ui.warn(_("%s:%d node does not exist\n") % (fn, ln)) |
|
148 | 148 | continue |
|
149 | 149 | r = repo.changelog.rev(n) |
|
150 | 150 | keys = getkeys(ui, repo, mygpg, data, context) |
|
151 | 151 | if not keys: |
|
152 | 152 | continue |
|
153 | 153 | revs.setdefault(r, []) |
|
154 | 154 | revs[r].extend(keys) |
|
155 | 155 | for rev in sorted(revs, reverse=True): |
|
156 | 156 | for k in revs[rev]: |
|
157 | 157 | r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev))) |
|
158 | 158 | ui.write("%-30s %s\n" % (keystr(ui, k), r)) |
|
159 | 159 | |
|
160 | 160 | def check(ui, repo, rev): |
|
161 | 161 | """verify all the signatures there may be for a particular revision""" |
|
162 | 162 | mygpg = newgpg(ui) |
|
163 | 163 | rev = repo.lookup(rev) |
|
164 | 164 | hexrev = hgnode.hex(rev) |
|
165 | 165 | keys = [] |
|
166 | 166 | |
|
167 | 167 | for data, context in sigwalk(repo): |
|
168 | 168 | node, version, sig = data |
|
169 | 169 | if node == hexrev: |
|
170 | 170 | k = getkeys(ui, repo, mygpg, data, context) |
|
171 | 171 | if k: |
|
172 | 172 | keys.extend(k) |
|
173 | 173 | |
|
174 | 174 | if not keys: |
|
175 | 175 | ui.write(_("No valid signature for %s\n") % hgnode.short(rev)) |
|
176 | 176 | return |
|
177 | 177 | |
|
178 | 178 | # print summary |
|
179 | 179 | ui.write("%s is signed by:\n" % hgnode.short(rev)) |
|
180 | 180 | for key in keys: |
|
181 | 181 | ui.write(" %s\n" % keystr(ui, key)) |
|
182 | 182 | |
|
183 | 183 | def keystr(ui, key): |
|
184 | 184 | """associate a string to a key (username, comment)""" |
|
185 | 185 | keyid, user, fingerprint = key |
|
186 | 186 | comment = ui.config("gpg", fingerprint, None) |
|
187 | 187 | if comment: |
|
188 | 188 | return "%s (%s)" % (user, comment) |
|
189 | 189 | else: |
|
190 | 190 | return user |
|
191 | 191 | |
|
192 | 192 | def sign(ui, repo, *revs, **opts): |
|
193 | 193 | """add a signature for the current or given revision |
|
194 | 194 | |
|
195 | 195 | If no revision is given, the parent of the working directory is used, |
|
196 | 196 | or tip if no revision is checked out. |
|
197 | 197 | |
|
198 | 198 | See 'hg help dates' for a list of formats valid for -d/--date. |
|
199 | 199 | """ |
|
200 | 200 | |
|
201 | 201 | mygpg = newgpg(ui, **opts) |
|
202 | 202 | sigver = "0" |
|
203 | 203 | sigmessage = "" |
|
204 | 204 | |
|
205 | 205 | date = opts.get('date') |
|
206 | 206 | if date: |
|
207 | 207 | opts['date'] = util.parsedate(date) |
|
208 | 208 | |
|
209 | 209 | if revs: |
|
210 | 210 | nodes = [repo.lookup(n) for n in revs] |
|
211 | 211 | else: |
|
212 | 212 | nodes = [node for node in repo.dirstate.parents() |
|
213 | 213 | if node != hgnode.nullid] |
|
214 | 214 | if len(nodes) > 1: |
|
215 | 215 | raise util.Abort(_('uncommitted merge - please provide a ' |
|
216 | 216 | 'specific revision')) |
|
217 | 217 | if not nodes: |
|
218 | 218 | nodes = [repo.changelog.tip()] |
|
219 | 219 | |
|
220 | 220 | for n in nodes: |
|
221 | 221 | hexnode = hgnode.hex(n) |
|
222 | 222 | ui.write("Signing %d:%s\n" % (repo.changelog.rev(n), |
|
223 | 223 | hgnode.short(n))) |
|
224 | 224 | # build data |
|
225 | 225 | data = node2txt(repo, n, sigver) |
|
226 | 226 | sig = mygpg.sign(data) |
|
227 | 227 | if not sig: |
|
228 | 228 | raise util.Abort(_("Error while signing")) |
|
229 | 229 | sig = binascii.b2a_base64(sig) |
|
230 | 230 | sig = sig.replace("\n", "") |
|
231 | 231 | sigmessage += "%s %s %s\n" % (hexnode, sigver, sig) |
|
232 | 232 | |
|
233 | 233 | # write it |
|
234 | 234 | if opts['local']: |
|
235 | 235 | repo.opener("localsigs", "ab").write(sigmessage) |
|
236 | 236 | return |
|
237 | 237 | |
|
238 | 238 | for x in repo.status(unknown=True)[:5]: |
|
239 | 239 | if ".hgsigs" in x and not opts["force"]: |
|
240 | 240 | raise util.Abort(_("working copy of .hgsigs is changed " |
|
241 | 241 | "(please commit .hgsigs manually " |
|
242 | 242 | "or use --force)")) |
|
243 | 243 | |
|
244 | 244 | repo.wfile(".hgsigs", "ab").write(sigmessage) |
|
245 | 245 | |
|
246 | 246 | if '.hgsigs' not in repo.dirstate: |
|
247 | 247 | repo.add([".hgsigs"]) |
|
248 | 248 | |
|
249 | 249 | if opts["no_commit"]: |
|
250 | 250 | return |
|
251 | 251 | |
|
252 | 252 | message = opts['message'] |
|
253 | 253 | if not message: |
|
254 | 254 | message = "\n".join([_("Added signature for changeset %s") |
|
255 | 255 | % hgnode.short(n) |
|
256 | 256 | for n in nodes]) |
|
257 | 257 | try: |
|
258 | 258 | m = match.exact(repo.root, '', ['.hgsigs']) |
|
259 | 259 | repo.commit(message, opts['user'], opts['date'], match=m) |
|
260 | 260 | except ValueError, inst: |
|
261 | 261 | raise util.Abort(str(inst)) |
|
262 | 262 | |
|
263 | 263 | def node2txt(repo, node, ver): |
|
264 | 264 | """map a manifest into some text""" |
|
265 | 265 | if ver == "0": |
|
266 | 266 | return "%s\n" % hgnode.hex(node) |
|
267 | 267 | else: |
|
268 | 268 | raise util.Abort(_("unknown signature version")) |
|
269 | 269 | |
|
270 | 270 | cmdtable = { |
|
271 | 271 | "sign": |
|
272 | 272 | (sign, |
|
273 | 273 | [('l', 'local', None, _('make the signature local')), |
|
274 | 274 | ('f', 'force', None, _('sign even if the sigfile is modified')), |
|
275 | 275 | ('', 'no-commit', None, _('do not commit the sigfile after signing')), |
|
276 | 276 | ('k', 'key', '', _('the key id to sign with')), |
|
277 | 277 | ('m', 'message', '', _('commit message')), |
|
278 | 278 | ] + commands.commitopts2, |
|
279 | 279 | _('hg sign [OPTION]... [REVISION]...')), |
|
280 | 280 | "sigcheck": (check, [], _('hg sigcheck REVISION')), |
|
281 | 281 | "sigs": (sigs, [], _('hg sigs')), |
|
282 | 282 | } |
|
283 | 283 |
@@ -1,97 +1,96 b'' | |||
|
1 | 1 | # Mercurial extension to make it easy to refer to the parent of a revision |
|
2 | 2 | # |
|
3 | 3 | # Copyright (C) 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br> |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2, incorporated herein by reference. |
|
7 | 7 | |
|
8 | '''\ | |
|
9 | use suffixes to refer to ancestor revisions | |
|
8 | '''use suffixes to refer to ancestor revisions | |
|
10 | 9 | |
|
11 | 10 | This extension allows you to use git-style suffixes to refer to the |
|
12 | 11 | ancestors of a specific revision. |
|
13 | 12 | |
|
14 | 13 | For example, if you can refer to a revision as "foo", then: |
|
15 | 14 | |
|
16 | 15 | - foo^N = Nth parent of foo |
|
17 | 16 | foo^0 = foo |
|
18 | 17 | foo^1 = first parent of foo |
|
19 | 18 | foo^2 = second parent of foo |
|
20 | 19 | foo^ = foo^1 |
|
21 | 20 | |
|
22 | 21 | - foo~N = Nth first grandparent of foo |
|
23 | 22 | foo~0 = foo |
|
24 | 23 | foo~1 = foo^1 = foo^ = first parent of foo |
|
25 | 24 | foo~2 = foo^1^1 = foo^^ = first parent of first parent of foo |
|
26 | 25 | ''' |
|
27 | 26 | from mercurial import error |
|
28 | 27 | |
|
29 | 28 | def reposetup(ui, repo): |
|
30 | 29 | if not repo.local(): |
|
31 | 30 | return |
|
32 | 31 | |
|
33 | 32 | class parentrevspecrepo(repo.__class__): |
|
34 | 33 | def lookup(self, key): |
|
35 | 34 | try: |
|
36 | 35 | _super = super(parentrevspecrepo, self) |
|
37 | 36 | return _super.lookup(key) |
|
38 | 37 | except error.RepoError: |
|
39 | 38 | pass |
|
40 | 39 | |
|
41 | 40 | circ = key.find('^') |
|
42 | 41 | tilde = key.find('~') |
|
43 | 42 | if circ < 0 and tilde < 0: |
|
44 | 43 | raise |
|
45 | 44 | elif circ >= 0 and tilde >= 0: |
|
46 | 45 | end = min(circ, tilde) |
|
47 | 46 | else: |
|
48 | 47 | end = max(circ, tilde) |
|
49 | 48 | |
|
50 | 49 | cl = self.changelog |
|
51 | 50 | base = key[:end] |
|
52 | 51 | try: |
|
53 | 52 | node = _super.lookup(base) |
|
54 | 53 | except error.RepoError: |
|
55 | 54 | # eek - reraise the first error |
|
56 | 55 | return _super.lookup(key) |
|
57 | 56 | |
|
58 | 57 | rev = cl.rev(node) |
|
59 | 58 | suffix = key[end:] |
|
60 | 59 | i = 0 |
|
61 | 60 | while i < len(suffix): |
|
62 | 61 | # foo^N => Nth parent of foo |
|
63 | 62 | # foo^0 == foo |
|
64 | 63 | # foo^1 == foo^ == 1st parent of foo |
|
65 | 64 | # foo^2 == 2nd parent of foo |
|
66 | 65 | if suffix[i] == '^': |
|
67 | 66 | j = i + 1 |
|
68 | 67 | p = cl.parentrevs(rev) |
|
69 | 68 | if j < len(suffix) and suffix[j].isdigit(): |
|
70 | 69 | j += 1 |
|
71 | 70 | n = int(suffix[i+1:j]) |
|
72 | 71 | if n > 2 or n == 2 and p[1] == -1: |
|
73 | 72 | raise |
|
74 | 73 | else: |
|
75 | 74 | n = 1 |
|
76 | 75 | if n: |
|
77 | 76 | rev = p[n - 1] |
|
78 | 77 | i = j |
|
79 | 78 | # foo~N => Nth first grandparent of foo |
|
80 | 79 | # foo~0 = foo |
|
81 | 80 | # foo~1 = foo^1 == foo^ == 1st parent of foo |
|
82 | 81 | # foo~2 = foo^1^1 == foo^^ == 1st parent of 1st parent of foo |
|
83 | 82 | elif suffix[i] == '~': |
|
84 | 83 | j = i + 1 |
|
85 | 84 | while j < len(suffix) and suffix[j].isdigit(): |
|
86 | 85 | j += 1 |
|
87 | 86 | if j == i + 1: |
|
88 | 87 | raise |
|
89 | 88 | n = int(suffix[i+1:j]) |
|
90 | 89 | for k in xrange(n): |
|
91 | 90 | rev = cl.parentrevs(rev)[0] |
|
92 | 91 | i = j |
|
93 | 92 | else: |
|
94 | 93 | raise |
|
95 | 94 | return cl.node(rev) |
|
96 | 95 | |
|
97 | 96 | repo.__class__ = parentrevspecrepo |
@@ -1,106 +1,108 b'' | |||
|
1 | 1 | # Copyright (C) 2006 - Marco Barisione <marco@barisione.org> |
|
2 | 2 | # |
|
3 | 3 | # This is a small extension for Mercurial (http://www.selenic.com/mercurial) |
|
4 | 4 | # that removes files not known to mercurial |
|
5 | 5 | # |
|
6 | 6 | # This program was inspired by the "cvspurge" script contained in CVS utilities |
|
7 | 7 | # (http://www.red-bean.com/cvsutils/). |
|
8 | 8 | # |
|
9 | 9 | # For help on the usage of "hg purge" use: |
|
10 | 10 | # hg help purge |
|
11 | 11 | # |
|
12 | 12 | # This program is free software; you can redistribute it and/or modify |
|
13 | 13 | # it under the terms of the GNU General Public License as published by |
|
14 | 14 | # the Free Software Foundation; either version 2 of the License, or |
|
15 | 15 | # (at your option) any later version. |
|
16 | 16 | # |
|
17 | 17 | # This program is distributed in the hope that it will be useful, |
|
18 | 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
19 | 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
20 | 20 | # GNU General Public License for more details. |
|
21 | 21 | # |
|
22 | 22 | # You should have received a copy of the GNU General Public License |
|
23 | 23 | # along with this program; if not, write to the Free Software |
|
24 | 24 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
25 | 25 | |
|
26 | '''enable removing untracked files only''' | |
|
27 | ||
|
26 | 28 | from mercurial import util, commands, cmdutil |
|
27 | 29 | from mercurial.i18n import _ |
|
28 | 30 | import os, stat |
|
29 | 31 | |
|
30 | 32 | def purge(ui, repo, *dirs, **opts): |
|
31 | 33 | '''removes files not tracked by Mercurial |
|
32 | 34 | |
|
33 | 35 | Delete files not known to Mercurial. This is useful to test local |
|
34 | 36 | and uncommitted changes in an otherwise-clean source tree. |
|
35 | 37 | |
|
36 | 38 | This means that purge will delete: |
|
37 | 39 | - Unknown files: files marked with "?" by "hg status" |
|
38 | 40 | - Empty directories: in fact Mercurial ignores directories unless |
|
39 | 41 | they contain files under source control management |
|
40 | 42 | But it will leave untouched: |
|
41 | 43 | - Modified and unmodified tracked files |
|
42 | 44 | - Ignored files (unless --all is specified) |
|
43 | 45 | - New files added to the repository (with "hg add") |
|
44 | 46 | |
|
45 | 47 | If directories are given on the command line, only files in these |
|
46 | 48 | directories are considered. |
|
47 | 49 | |
|
48 | 50 | Be careful with purge, as you could irreversibly delete some files |
|
49 | 51 | you forgot to add to the repository. If you only want to print the |
|
50 | 52 | list of files that this program would delete, use the --print |
|
51 | 53 | option. |
|
52 | 54 | ''' |
|
53 | 55 | act = not opts['print'] |
|
54 | 56 | eol = '\n' |
|
55 | 57 | if opts['print0']: |
|
56 | 58 | eol = '\0' |
|
57 | 59 | act = False # --print0 implies --print |
|
58 | 60 | |
|
59 | 61 | def remove(remove_func, name): |
|
60 | 62 | if act: |
|
61 | 63 | try: |
|
62 | 64 | remove_func(repo.wjoin(name)) |
|
63 | 65 | except OSError: |
|
64 | 66 | m = _('%s cannot be removed') % name |
|
65 | 67 | if opts['abort_on_err']: |
|
66 | 68 | raise util.Abort(m) |
|
67 | 69 | ui.warn(_('warning: %s\n') % m) |
|
68 | 70 | else: |
|
69 | 71 | ui.write('%s%s' % (name, eol)) |
|
70 | 72 | |
|
71 | 73 | def removefile(path): |
|
72 | 74 | try: |
|
73 | 75 | os.remove(path) |
|
74 | 76 | except OSError: |
|
75 | 77 | # read-only files cannot be unlinked under Windows |
|
76 | 78 | s = os.stat(path) |
|
77 | 79 | if (s.st_mode & stat.S_IWRITE) != 0: |
|
78 | 80 | raise |
|
79 | 81 | os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE) |
|
80 | 82 | os.remove(path) |
|
81 | 83 | |
|
82 | 84 | directories = [] |
|
83 | 85 | match = cmdutil.match(repo, dirs, opts) |
|
84 | 86 | match.dir = directories.append |
|
85 | 87 | status = repo.status(match=match, ignored=opts['all'], unknown=True) |
|
86 | 88 | |
|
87 | 89 | for f in sorted(status[4] + status[5]): |
|
88 | 90 | ui.note(_('Removing file %s\n') % f) |
|
89 | 91 | remove(removefile, f) |
|
90 | 92 | |
|
91 | 93 | for f in sorted(directories, reverse=True): |
|
92 | 94 | if match(f) and not os.listdir(repo.wjoin(f)): |
|
93 | 95 | ui.note(_('Removing directory %s\n') % f) |
|
94 | 96 | remove(os.rmdir, f) |
|
95 | 97 | |
|
96 | 98 | cmdtable = { |
|
97 | 99 | 'purge|clean': |
|
98 | 100 | (purge, |
|
99 | 101 | [('a', 'abort-on-err', None, _('abort if an error occurs')), |
|
100 | 102 | ('', 'all', None, _('purge ignored files too')), |
|
101 | 103 | ('p', 'print', None, _('print filenames instead of deleting them')), |
|
102 | 104 | ('0', 'print0', None, _('end filenames with NUL, for use with xargs' |
|
103 | 105 | ' (implies -p/--print)')), |
|
104 | 106 | ] + commands.walkopts, |
|
105 | 107 | _('hg purge [OPTION]... [DIR]...')) |
|
106 | 108 | } |
@@ -1,31 +1,31 b'' | |||
|
1 | # Mercurial extension to provide the 'hg share' command | |
|
2 | # | |
|
3 | 1 |
|
|
4 | 2 | # |
|
5 | 3 | # This software may be used and distributed according to the terms of the |
|
6 | 4 | # GNU General Public License version 2, incorporated herein by reference. |
|
7 | 5 | |
|
6 | '''provides the hg share command''' | |
|
7 | ||
|
8 | 8 | import os |
|
9 | 9 | from mercurial.i18n import _ |
|
10 | 10 | from mercurial import hg, commands |
|
11 | 11 | |
|
12 | 12 | def share(ui, source, dest=None, noupdate=False): |
|
13 | 13 | """create a new shared repository (experimental) |
|
14 | 14 | |
|
15 | 15 | Initialize a new repository and working directory that shares its |
|
16 | 16 | history with another repository. |
|
17 | 17 | |
|
18 | 18 | NOTE: actions that change history such as rollback or moving the |
|
19 | 19 | source may confuse sharers. |
|
20 | 20 | """ |
|
21 | 21 | |
|
22 | 22 | return hg.share(ui, source, dest, not noupdate) |
|
23 | 23 | |
|
24 | 24 | cmdtable = { |
|
25 | 25 | "share": |
|
26 | 26 | (share, |
|
27 | 27 | [('U', 'noupdate', None, _('do not create a working copy'))], |
|
28 | 28 | _('[-U] SOURCE [DEST]')), |
|
29 | 29 | } |
|
30 | 30 | |
|
31 | 31 | commands.norepo += " share" |
@@ -1,155 +1,158 b'' | |||
|
1 | 1 | # win32text.py - LF <-> CRLF/CR translation utilities for Windows/Mac users |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2005, 2007-2009 Matt Mackall <mpm@selenic.com> and others |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2, incorporated herein by reference. |
|
7 | # | |
|
8 | # To perform automatic newline conversion, use: | |
|
9 | # | |
|
10 | # [extensions] | |
|
11 | # hgext.win32text = | |
|
12 | # [encode] | |
|
13 | # ** = cleverencode: | |
|
14 | # # or ** = macencode: | |
|
15 | # [decode] | |
|
16 |
# |
|
|
17 | # # or ** = macdecode: | |
|
18 | # | |
|
19 | # If not doing conversion, to make sure you do not commit CRLF/CR by | |
|
20 | # accident: | |
|
21 | # | |
|
22 | # [hooks] | |
|
23 | # pretxncommit.crlf = python:hgext.win32text.forbidcrlf | |
|
24 | # # or pretxncommit.cr = python:hgext.win32text.forbidcr | |
|
25 | # | |
|
26 | # To do the same check on a server to prevent CRLF/CR from being | |
|
27 | # pushed or pulled: | |
|
28 | # | |
|
29 | # [hooks] | |
|
30 | # pretxnchangegroup.crlf = python:hgext.win32text.forbidcrlf | |
|
31 | # # or pretxnchangegroup.cr = python:hgext.win32text.forbidcr | |
|
7 | ||
|
8 | '''LF <-> CRLF/CR translation utilities | |
|
9 | ||
|
10 | To perform automatic newline conversion, use: | |
|
11 | ||
|
12 | [extensions] | |
|
13 | hgext.win32text = | |
|
14 | [encode] | |
|
15 | ** = cleverencode: | |
|
16 | # or ** = macencode: | |
|
17 | ||
|
18 | [decode] | |
|
19 | ** = cleverdecode: | |
|
20 | # or ** = macdecode: | |
|
21 | ||
|
22 | If not doing conversion, to make sure you do not commit CRLF/CR by accident: | |
|
23 | ||
|
24 | [hooks] | |
|
25 | pretxncommit.crlf = python:hgext.win32text.forbidcrlf | |
|
26 | # or pretxncommit.cr = python:hgext.win32text.forbidcr | |
|
27 | ||
|
28 | To do the same check on a server to prevent CRLF/CR from being | |
|
29 | pushed or pulled: | |
|
30 | ||
|
31 | [hooks] | |
|
32 | pretxnchangegroup.crlf = python:hgext.win32text.forbidcrlf | |
|
33 | # or pretxnchangegroup.cr = python:hgext.win32text.forbidcr | |
|
34 | ''' | |
|
32 | 35 | |
|
33 | 36 | from mercurial.i18n import _ |
|
34 | 37 | from mercurial.node import short |
|
35 | 38 | from mercurial import util |
|
36 | 39 | import re |
|
37 | 40 | |
|
38 | 41 | # regexp for single LF without CR preceding. |
|
39 | 42 | re_single_lf = re.compile('(^|[^\r])\n', re.MULTILINE) |
|
40 | 43 | |
|
41 | 44 | newlinestr = {'\r\n': 'CRLF', '\r': 'CR'} |
|
42 | 45 | filterstr = {'\r\n': 'clever', '\r': 'mac'} |
|
43 | 46 | |
|
44 | 47 | def checknewline(s, newline, ui=None, repo=None, filename=None): |
|
45 | 48 | # warn if already has 'newline' in repository. |
|
46 | 49 | # it might cause unexpected eol conversion. |
|
47 | 50 | # see issue 302: |
|
48 | 51 | # http://www.selenic.com/mercurial/bts/issue302 |
|
49 | 52 | if newline in s and ui and filename and repo: |
|
50 | 53 | ui.warn(_('WARNING: %s already has %s line endings\n' |
|
51 | 54 | 'and does not need EOL conversion by the win32text plugin.\n' |
|
52 | 55 | 'Before your next commit, please reconsider your ' |
|
53 | 56 | 'encode/decode settings in \nMercurial.ini or %s.\n') % |
|
54 | 57 | (filename, newlinestr[newline], repo.join('hgrc'))) |
|
55 | 58 | |
|
56 | 59 | def dumbdecode(s, cmd, **kwargs): |
|
57 | 60 | checknewline(s, '\r\n', **kwargs) |
|
58 | 61 | # replace single LF to CRLF |
|
59 | 62 | return re_single_lf.sub('\\1\r\n', s) |
|
60 | 63 | |
|
61 | 64 | def dumbencode(s, cmd): |
|
62 | 65 | return s.replace('\r\n', '\n') |
|
63 | 66 | |
|
64 | 67 | def macdumbdecode(s, cmd, **kwargs): |
|
65 | 68 | checknewline(s, '\r', **kwargs) |
|
66 | 69 | return s.replace('\n', '\r') |
|
67 | 70 | |
|
68 | 71 | def macdumbencode(s, cmd): |
|
69 | 72 | return s.replace('\r', '\n') |
|
70 | 73 | |
|
71 | 74 | def cleverdecode(s, cmd, **kwargs): |
|
72 | 75 | if not util.binary(s): |
|
73 | 76 | return dumbdecode(s, cmd, **kwargs) |
|
74 | 77 | return s |
|
75 | 78 | |
|
76 | 79 | def cleverencode(s, cmd): |
|
77 | 80 | if not util.binary(s): |
|
78 | 81 | return dumbencode(s, cmd) |
|
79 | 82 | return s |
|
80 | 83 | |
|
81 | 84 | def macdecode(s, cmd, **kwargs): |
|
82 | 85 | if not util.binary(s): |
|
83 | 86 | return macdumbdecode(s, cmd, **kwargs) |
|
84 | 87 | return s |
|
85 | 88 | |
|
86 | 89 | def macencode(s, cmd): |
|
87 | 90 | if not util.binary(s): |
|
88 | 91 | return macdumbencode(s, cmd) |
|
89 | 92 | return s |
|
90 | 93 | |
|
91 | 94 | _filters = { |
|
92 | 95 | 'dumbdecode:': dumbdecode, |
|
93 | 96 | 'dumbencode:': dumbencode, |
|
94 | 97 | 'cleverdecode:': cleverdecode, |
|
95 | 98 | 'cleverencode:': cleverencode, |
|
96 | 99 | 'macdumbdecode:': macdumbdecode, |
|
97 | 100 | 'macdumbencode:': macdumbencode, |
|
98 | 101 | 'macdecode:': macdecode, |
|
99 | 102 | 'macencode:': macencode, |
|
100 | 103 | } |
|
101 | 104 | |
|
102 | 105 | def forbidnewline(ui, repo, hooktype, node, newline, **kwargs): |
|
103 | 106 | halt = False |
|
104 | 107 | seen = set() |
|
105 | 108 | # we try to walk changesets in reverse order from newest to |
|
106 | 109 | # oldest, so that if we see a file multiple times, we take the |
|
107 | 110 | # newest version as canonical. this prevents us from blocking a |
|
108 | 111 | # changegroup that contains an unacceptable commit followed later |
|
109 | 112 | # by a commit that fixes the problem. |
|
110 | 113 | tip = repo['tip'] |
|
111 | 114 | for rev in xrange(len(repo)-1, repo[node].rev()-1, -1): |
|
112 | 115 | c = repo[rev] |
|
113 | 116 | for f in c.files(): |
|
114 | 117 | if f in seen or f not in tip or f not in c: |
|
115 | 118 | continue |
|
116 | 119 | seen.add(f) |
|
117 | 120 | data = c[f].data() |
|
118 | 121 | if not util.binary(data) and newline in data: |
|
119 | 122 | if not halt: |
|
120 | 123 | ui.warn(_('Attempt to commit or push text file(s) ' |
|
121 | 124 | 'using %s line endings\n') % |
|
122 | 125 | newlinestr[newline]) |
|
123 | 126 | ui.warn(_('in %s: %s\n') % (short(c.node()), f)) |
|
124 | 127 | halt = True |
|
125 | 128 | if halt and hooktype == 'pretxnchangegroup': |
|
126 | 129 | crlf = newlinestr[newline].lower() |
|
127 | 130 | filter = filterstr[newline] |
|
128 | 131 | ui.warn(_('\nTo prevent this mistake in your local repository,\n' |
|
129 | 132 | 'add to Mercurial.ini or .hg/hgrc:\n' |
|
130 | 133 | '\n' |
|
131 | 134 | '[hooks]\n' |
|
132 | 135 | 'pretxncommit.%s = python:hgext.win32text.forbid%s\n' |
|
133 | 136 | '\n' |
|
134 | 137 | 'and also consider adding:\n' |
|
135 | 138 | '\n' |
|
136 | 139 | '[extensions]\n' |
|
137 | 140 | 'hgext.win32text =\n' |
|
138 | 141 | '[encode]\n' |
|
139 | 142 | '** = %sencode:\n' |
|
140 | 143 | '[decode]\n' |
|
141 | 144 | '** = %sdecode:\n') % (crlf, crlf, filter, filter)) |
|
142 | 145 | return halt |
|
143 | 146 | |
|
144 | 147 | def forbidcrlf(ui, repo, hooktype, node, **kwargs): |
|
145 | 148 | return forbidnewline(ui, repo, hooktype, node, '\r\n', **kwargs) |
|
146 | 149 | |
|
147 | 150 | def forbidcr(ui, repo, hooktype, node, **kwargs): |
|
148 | 151 | return forbidnewline(ui, repo, hooktype, node, '\r', **kwargs) |
|
149 | 152 | |
|
150 | 153 | def reposetup(ui, repo): |
|
151 | 154 | if not repo.local(): |
|
152 | 155 | return |
|
153 | 156 | for name, fn in _filters.iteritems(): |
|
154 | 157 | repo.adddatafilter(name, fn) |
|
155 | 158 |
General Comments 0
You need to be logged in to leave comments.
Login now