##// END OF EJS Templates
merge with stable
Martin Geisler -
r13127:ece1c069 merge default
parent child Browse files
Show More
@@ -1,197 +1,198 b''
1 # churn.py - create a graph of revisions count grouped by template
1 # churn.py - create a graph of revisions count grouped by template
2 #
2 #
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''command to display statistics about repository history'''
9 '''command to display statistics about repository history'''
10
10
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12 from mercurial import patch, cmdutil, util, templater, commands
12 from mercurial import patch, cmdutil, util, templater, commands
13 import os
13 import os
14 import time, datetime
14 import time, datetime
15
15
16 def maketemplater(ui, repo, tmpl):
16 def maketemplater(ui, repo, tmpl):
17 tmpl = templater.parsestring(tmpl, quoted=False)
17 tmpl = templater.parsestring(tmpl, quoted=False)
18 try:
18 try:
19 t = cmdutil.changeset_templater(ui, repo, False, None, None, False)
19 t = cmdutil.changeset_templater(ui, repo, False, None, None, False)
20 except SyntaxError, inst:
20 except SyntaxError, inst:
21 raise util.Abort(inst.args[0])
21 raise util.Abort(inst.args[0])
22 t.use_template(tmpl)
22 t.use_template(tmpl)
23 return t
23 return t
24
24
25 def changedlines(ui, repo, ctx1, ctx2, fns):
25 def changedlines(ui, repo, ctx1, ctx2, fns):
26 added, removed = 0, 0
26 added, removed = 0, 0
27 fmatch = cmdutil.matchfiles(repo, fns)
27 fmatch = cmdutil.matchfiles(repo, fns)
28 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
28 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
29 for l in diff.split('\n'):
29 for l in diff.split('\n'):
30 if l.startswith("+") and not l.startswith("+++ "):
30 if l.startswith("+") and not l.startswith("+++ "):
31 added += 1
31 added += 1
32 elif l.startswith("-") and not l.startswith("--- "):
32 elif l.startswith("-") and not l.startswith("--- "):
33 removed += 1
33 removed += 1
34 return (added, removed)
34 return (added, removed)
35
35
36 def countrate(ui, repo, amap, *pats, **opts):
36 def countrate(ui, repo, amap, *pats, **opts):
37 """Calculate stats"""
37 """Calculate stats"""
38 if opts.get('dateformat'):
38 if opts.get('dateformat'):
39 def getkey(ctx):
39 def getkey(ctx):
40 t, tz = ctx.date()
40 t, tz = ctx.date()
41 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
41 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
42 return date.strftime(opts['dateformat'])
42 return date.strftime(opts['dateformat'])
43 else:
43 else:
44 tmpl = opts.get('template', '{author|email}')
44 tmpl = opts.get('template', '{author|email}')
45 tmpl = maketemplater(ui, repo, tmpl)
45 tmpl = maketemplater(ui, repo, tmpl)
46 def getkey(ctx):
46 def getkey(ctx):
47 ui.pushbuffer()
47 ui.pushbuffer()
48 tmpl.show(ctx)
48 tmpl.show(ctx)
49 return ui.popbuffer()
49 return ui.popbuffer()
50
50
51 state = {'count': 0}
51 state = {'count': 0}
52 rate = {}
52 rate = {}
53 df = False
53 df = False
54 if opts.get('date'):
54 if opts.get('date'):
55 df = util.matchdate(opts['date'])
55 df = util.matchdate(opts['date'])
56
56
57 m = cmdutil.match(repo, pats, opts)
57 m = cmdutil.match(repo, pats, opts)
58 def prep(ctx, fns):
58 def prep(ctx, fns):
59 rev = ctx.rev()
59 rev = ctx.rev()
60 if df and not df(ctx.date()[0]): # doesn't match date format
60 if df and not df(ctx.date()[0]): # doesn't match date format
61 return
61 return
62
62
63 key = getkey(ctx)
63 key = getkey(ctx)
64 key = amap.get(key, key) # alias remap
64 key = amap.get(key, key) # alias remap
65 key = key.strip() # ignore leading and trailing spaces
65 if opts.get('changesets'):
66 if opts.get('changesets'):
66 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
67 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
67 else:
68 else:
68 parents = ctx.parents()
69 parents = ctx.parents()
69 if len(parents) > 1:
70 if len(parents) > 1:
70 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
71 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
71 return
72 return
72
73
73 ctx1 = parents[0]
74 ctx1 = parents[0]
74 lines = changedlines(ui, repo, ctx1, ctx, fns)
75 lines = changedlines(ui, repo, ctx1, ctx, fns)
75 rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
76 rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
76
77
77 state['count'] += 1
78 state['count'] += 1
78 ui.progress(_('analyzing'), state['count'], total=len(repo))
79 ui.progress(_('analyzing'), state['count'], total=len(repo))
79
80
80 for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
81 for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
81 continue
82 continue
82
83
83 ui.progress(_('analyzing'), None)
84 ui.progress(_('analyzing'), None)
84
85
85 return rate
86 return rate
86
87
87
88
88 def churn(ui, repo, *pats, **opts):
89 def churn(ui, repo, *pats, **opts):
89 '''histogram of changes to the repository
90 '''histogram of changes to the repository
90
91
91 This command will display a histogram representing the number
92 This command will display a histogram representing the number
92 of changed lines or revisions, grouped according to the given
93 of changed lines or revisions, grouped according to the given
93 template. The default template will group changes by author.
94 template. The default template will group changes by author.
94 The --dateformat option may be used to group the results by
95 The --dateformat option may be used to group the results by
95 date instead.
96 date instead.
96
97
97 Statistics are based on the number of changed lines, or
98 Statistics are based on the number of changed lines, or
98 alternatively the number of matching revisions if the
99 alternatively the number of matching revisions if the
99 --changesets option is specified.
100 --changesets option is specified.
100
101
101 Examples::
102 Examples::
102
103
103 # display count of changed lines for every committer
104 # display count of changed lines for every committer
104 hg churn -t '{author|email}'
105 hg churn -t '{author|email}'
105
106
106 # display daily activity graph
107 # display daily activity graph
107 hg churn -f '%H' -s -c
108 hg churn -f '%H' -s -c
108
109
109 # display activity of developers by month
110 # display activity of developers by month
110 hg churn -f '%Y-%m' -s -c
111 hg churn -f '%Y-%m' -s -c
111
112
112 # display count of lines changed in every year
113 # display count of lines changed in every year
113 hg churn -f '%Y' -s
114 hg churn -f '%Y' -s
114
115
115 It is possible to map alternate email addresses to a main address
116 It is possible to map alternate email addresses to a main address
116 by providing a file using the following format::
117 by providing a file using the following format::
117
118
118 <alias email> = <actual email>
119 <alias email> = <actual email>
119
120
120 Such a file may be specified with the --aliases option, otherwise
121 Such a file may be specified with the --aliases option, otherwise
121 a .hgchurn file will be looked for in the working directory root.
122 a .hgchurn file will be looked for in the working directory root.
122 '''
123 '''
123 def pad(s, l):
124 def pad(s, l):
124 return (s + " " * l)[:l]
125 return (s + " " * l)[:l]
125
126
126 amap = {}
127 amap = {}
127 aliases = opts.get('aliases')
128 aliases = opts.get('aliases')
128 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
129 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
129 aliases = repo.wjoin('.hgchurn')
130 aliases = repo.wjoin('.hgchurn')
130 if aliases:
131 if aliases:
131 for l in open(aliases, "r"):
132 for l in open(aliases, "r"):
132 try:
133 try:
133 alias, actual = l.split('=' in l and '=' or None, 1)
134 alias, actual = l.split('=' in l and '=' or None, 1)
134 amap[alias.strip()] = actual.strip()
135 amap[alias.strip()] = actual.strip()
135 except ValueError:
136 except ValueError:
136 l = l.strip()
137 l = l.strip()
137 if l:
138 if l:
138 ui.warn(_("skipping malformed alias: %s\n" % l))
139 ui.warn(_("skipping malformed alias: %s\n" % l))
139 continue
140 continue
140
141
141 rate = countrate(ui, repo, amap, *pats, **opts).items()
142 rate = countrate(ui, repo, amap, *pats, **opts).items()
142 if not rate:
143 if not rate:
143 return
144 return
144
145
145 sortkey = ((not opts.get('sort')) and (lambda x: -sum(x[1])) or None)
146 sortkey = ((not opts.get('sort')) and (lambda x: -sum(x[1])) or None)
146 rate.sort(key=sortkey)
147 rate.sort(key=sortkey)
147
148
148 # Be careful not to have a zero maxcount (issue833)
149 # Be careful not to have a zero maxcount (issue833)
149 maxcount = float(max(sum(v) for k, v in rate)) or 1.0
150 maxcount = float(max(sum(v) for k, v in rate)) or 1.0
150 maxname = max(len(k) for k, v in rate)
151 maxname = max(len(k) for k, v in rate)
151
152
152 ttywidth = ui.termwidth()
153 ttywidth = ui.termwidth()
153 ui.debug("assuming %i character terminal\n" % ttywidth)
154 ui.debug("assuming %i character terminal\n" % ttywidth)
154 width = ttywidth - maxname - 2 - 2 - 2
155 width = ttywidth - maxname - 2 - 2 - 2
155
156
156 if opts.get('diffstat'):
157 if opts.get('diffstat'):
157 width -= 15
158 width -= 15
158 def format(name, diffstat):
159 def format(name, diffstat):
159 added, removed = diffstat
160 added, removed = diffstat
160 return "%s %15s %s%s\n" % (pad(name, maxname),
161 return "%s %15s %s%s\n" % (pad(name, maxname),
161 '+%d/-%d' % (added, removed),
162 '+%d/-%d' % (added, removed),
162 ui.label('+' * charnum(added),
163 ui.label('+' * charnum(added),
163 'diffstat.inserted'),
164 'diffstat.inserted'),
164 ui.label('-' * charnum(removed),
165 ui.label('-' * charnum(removed),
165 'diffstat.deleted'))
166 'diffstat.deleted'))
166 else:
167 else:
167 width -= 6
168 width -= 6
168 def format(name, count):
169 def format(name, count):
169 return "%s %6d %s\n" % (pad(name, maxname), sum(count),
170 return "%s %6d %s\n" % (pad(name, maxname), sum(count),
170 '*' * charnum(sum(count)))
171 '*' * charnum(sum(count)))
171
172
172 def charnum(count):
173 def charnum(count):
173 return int(round(count * width / maxcount))
174 return int(round(count * width / maxcount))
174
175
175 for name, count in rate:
176 for name, count in rate:
176 ui.write(format(name, count))
177 ui.write(format(name, count))
177
178
178
179
179 cmdtable = {
180 cmdtable = {
180 "churn":
181 "churn":
181 (churn,
182 (churn,
182 [('r', 'rev', [],
183 [('r', 'rev', [],
183 _('count rate for the specified revision or range'), _('REV')),
184 _('count rate for the specified revision or range'), _('REV')),
184 ('d', 'date', '',
185 ('d', 'date', '',
185 _('count rate for revisions matching date spec'), _('DATE')),
186 _('count rate for revisions matching date spec'), _('DATE')),
186 ('t', 'template', '{author|email}',
187 ('t', 'template', '{author|email}',
187 _('template to group changesets'), _('TEMPLATE')),
188 _('template to group changesets'), _('TEMPLATE')),
188 ('f', 'dateformat', '',
189 ('f', 'dateformat', '',
189 _('strftime-compatible format for grouping by date'), _('FORMAT')),
190 _('strftime-compatible format for grouping by date'), _('FORMAT')),
190 ('c', 'changesets', False, _('count rate by number of changesets')),
191 ('c', 'changesets', False, _('count rate by number of changesets')),
191 ('s', 'sort', False, _('sort by key (default: sort by count)')),
192 ('s', 'sort', False, _('sort by key (default: sort by count)')),
192 ('', 'diffstat', False, _('display added/removed lines separately')),
193 ('', 'diffstat', False, _('display added/removed lines separately')),
193 ('', 'aliases', '',
194 ('', 'aliases', '',
194 _('file with email aliases'), _('FILE')),
195 _('file with email aliases'), _('FILE')),
195 ] + commands.walkopts,
196 ] + commands.walkopts,
196 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]")),
197 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]")),
197 }
198 }
@@ -1,274 +1,280 b''
1 """automatically manage newlines in repository files
1 """automatically manage newlines in repository files
2
2
3 This extension allows you to manage the type of line endings (CRLF or
3 This extension allows you to manage the type of line endings (CRLF or
4 LF) that are used in the repository and in the local working
4 LF) that are used in the repository and in the local working
5 directory. That way you can get CRLF line endings on Windows and LF on
5 directory. That way you can get CRLF line endings on Windows and LF on
6 Unix/Mac, thereby letting everybody use their OS native line endings.
6 Unix/Mac, thereby letting everybody use their OS native line endings.
7
7
8 The extension reads its configuration from a versioned ``.hgeol``
8 The extension reads its configuration from a versioned ``.hgeol``
9 configuration file every time you run an ``hg`` command. The
9 configuration file every time you run an ``hg`` command. The
10 ``.hgeol`` file use the same syntax as all other Mercurial
10 ``.hgeol`` file use the same syntax as all other Mercurial
11 configuration files. It uses two sections, ``[patterns]`` and
11 configuration files. It uses two sections, ``[patterns]`` and
12 ``[repository]``.
12 ``[repository]``.
13
13
14 The ``[patterns]`` section specifies the line endings used in the
14 The ``[patterns]`` section specifies how line endings should be
15 working directory. The format is specified by a file pattern. The
15 converted between the working copy and the repository. The format is
16 first match is used, so put more specific patterns first. The
16 specified by a file pattern. The first match is used, so put more
17 available line endings are ``LF``, ``CRLF``, and ``BIN``.
17 specific patterns first. The available line endings are ``LF``,
18 ``CRLF``, and ``BIN``.
18
19
19 Files with the declared format of ``CRLF`` or ``LF`` are always
20 Files with the declared format of ``CRLF`` or ``LF`` are always
20 checked out in that format and files declared to be binary (``BIN``)
21 checked out and stored in the repository in that format and files
21 are left unchanged. Additionally, ``native`` is an alias for the
22 declared to be binary (``BIN``) are left unchanged. Additionally,
22 platform's default line ending: ``LF`` on Unix (including Mac OS X)
23 ``native`` is an alias for checking out in the platform's default line
23 and ``CRLF`` on Windows. Note that ``BIN`` (do nothing to line
24 ending: ``LF`` on Unix (including Mac OS X) and ``CRLF`` on
24 endings) is Mercurial's default behaviour; it is only needed if you
25 Windows. Note that ``BIN`` (do nothing to line endings) is Mercurial's
25 need to override a later, more general pattern.
26 default behaviour; it is only needed if you need to override a later,
27 more general pattern.
26
28
27 The optional ``[repository]`` section specifies the line endings to
29 The optional ``[repository]`` section specifies the line endings to
28 use for files stored in the repository. It has a single setting,
30 use for files stored in the repository. It has a single setting,
29 ``native``, which determines the storage line endings for files
31 ``native``, which determines the storage line endings for files
30 declared as ``native`` in the ``[patterns]`` section. It can be set to
32 declared as ``native`` in the ``[patterns]`` section. It can be set to
31 ``LF`` or ``CRLF``. The default is ``LF``. For example, this means
33 ``LF`` or ``CRLF``. The default is ``LF``. For example, this means
32 that on Windows, files configured as ``native`` (``CRLF`` by default)
34 that on Windows, files configured as ``native`` (``CRLF`` by default)
33 will be converted to ``LF`` when stored in the repository. Files
35 will be converted to ``LF`` when stored in the repository. Files
34 declared as ``LF``, ``CRLF``, or ``BIN`` in the ``[patterns]`` section
36 declared as ``LF``, ``CRLF``, or ``BIN`` in the ``[patterns]`` section
35 are always stored as-is in the repository.
37 are always stored as-is in the repository.
36
38
37 Example versioned ``.hgeol`` file::
39 Example versioned ``.hgeol`` file::
38
40
39 [patterns]
41 [patterns]
40 **.py = native
42 **.py = native
41 **.vcproj = CRLF
43 **.vcproj = CRLF
42 **.txt = native
44 **.txt = native
43 Makefile = LF
45 Makefile = LF
44 **.jpg = BIN
46 **.jpg = BIN
45
47
46 [repository]
48 [repository]
47 native = LF
49 native = LF
48
50
51 .. note::
52 The rules will first apply when files are touched in the working
53 copy, e.g. by updating to null and back to tip to touch all files.
54
49 The extension uses an optional ``[eol]`` section in your hgrc file
55 The extension uses an optional ``[eol]`` section in your hgrc file
50 (not the ``.hgeol`` file) for settings that control the overall
56 (not the ``.hgeol`` file) for settings that control the overall
51 behavior. There are two settings:
57 behavior. There are two settings:
52
58
53 - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
59 - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
54 ``CRLF`` to override the default interpretation of ``native`` for
60 ``CRLF`` to override the default interpretation of ``native`` for
55 checkout. This can be used with :hg:`archive` on Unix, say, to
61 checkout. This can be used with :hg:`archive` on Unix, say, to
56 generate an archive where files have line endings for Windows.
62 generate an archive where files have line endings for Windows.
57
63
58 - ``eol.only-consistent`` (default True) can be set to False to make
64 - ``eol.only-consistent`` (default True) can be set to False to make
59 the extension convert files with inconsistent EOLs. Inconsistent
65 the extension convert files with inconsistent EOLs. Inconsistent
60 means that there is both ``CRLF`` and ``LF`` present in the file.
66 means that there is both ``CRLF`` and ``LF`` present in the file.
61 Such files are normally not touched under the assumption that they
67 Such files are normally not touched under the assumption that they
62 have mixed EOLs on purpose.
68 have mixed EOLs on purpose.
63
69
64 The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
70 The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
65 like the deprecated win32text extension does. This means that you can
71 like the deprecated win32text extension does. This means that you can
66 disable win32text and enable eol and your filters will still work. You
72 disable win32text and enable eol and your filters will still work. You
67 only need to these filters until you have prepared a ``.hgeol`` file.
73 only need to these filters until you have prepared a ``.hgeol`` file.
68
74
69 The ``win32text.forbid*`` hooks provided by the win32text extension
75 The ``win32text.forbid*`` hooks provided by the win32text extension
70 have been unified into a single hook named ``eol.hook``. The hook will
76 have been unified into a single hook named ``eol.hook``. The hook will
71 lookup the expected line endings from the ``.hgeol`` file, which means
77 lookup the expected line endings from the ``.hgeol`` file, which means
72 you must migrate to a ``.hgeol`` file first before using the hook.
78 you must migrate to a ``.hgeol`` file first before using the hook.
73
79
74 See :hg:`help patterns` for more information about the glob patterns
80 See :hg:`help patterns` for more information about the glob patterns
75 used.
81 used.
76 """
82 """
77
83
78 from mercurial.i18n import _
84 from mercurial.i18n import _
79 from mercurial import util, config, extensions, match
85 from mercurial import util, config, extensions, match
80 import re, os
86 import re, os
81
87
82 # Matches a lone LF, i.e., one that is not part of CRLF.
88 # Matches a lone LF, i.e., one that is not part of CRLF.
83 singlelf = re.compile('(^|[^\r])\n')
89 singlelf = re.compile('(^|[^\r])\n')
84 # Matches a single EOL which can either be a CRLF where repeated CR
90 # Matches a single EOL which can either be a CRLF where repeated CR
85 # are removed or a LF. We do not care about old Machintosh files, so a
91 # are removed or a LF. We do not care about old Machintosh files, so a
86 # stray CR is an error.
92 # stray CR is an error.
87 eolre = re.compile('\r*\n')
93 eolre = re.compile('\r*\n')
88
94
89
95
90 def inconsistenteol(data):
96 def inconsistenteol(data):
91 return '\r\n' in data and singlelf.search(data)
97 return '\r\n' in data and singlelf.search(data)
92
98
93 def tolf(s, params, ui, **kwargs):
99 def tolf(s, params, ui, **kwargs):
94 """Filter to convert to LF EOLs."""
100 """Filter to convert to LF EOLs."""
95 if util.binary(s):
101 if util.binary(s):
96 return s
102 return s
97 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
103 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
98 return s
104 return s
99 return eolre.sub('\n', s)
105 return eolre.sub('\n', s)
100
106
101 def tocrlf(s, params, ui, **kwargs):
107 def tocrlf(s, params, ui, **kwargs):
102 """Filter to convert to CRLF EOLs."""
108 """Filter to convert to CRLF EOLs."""
103 if util.binary(s):
109 if util.binary(s):
104 return s
110 return s
105 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
111 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
106 return s
112 return s
107 return eolre.sub('\r\n', s)
113 return eolre.sub('\r\n', s)
108
114
109 def isbinary(s, params):
115 def isbinary(s, params):
110 """Filter to do nothing with the file."""
116 """Filter to do nothing with the file."""
111 return s
117 return s
112
118
113 filters = {
119 filters = {
114 'to-lf': tolf,
120 'to-lf': tolf,
115 'to-crlf': tocrlf,
121 'to-crlf': tocrlf,
116 'is-binary': isbinary,
122 'is-binary': isbinary,
117 # The following provide backwards compatibility with win32text
123 # The following provide backwards compatibility with win32text
118 'cleverencode:': tolf,
124 'cleverencode:': tolf,
119 'cleverdecode:': tocrlf
125 'cleverdecode:': tocrlf
120 }
126 }
121
127
122
128
123 def hook(ui, repo, node, hooktype, **kwargs):
129 def hook(ui, repo, node, hooktype, **kwargs):
124 """verify that files have expected EOLs"""
130 """verify that files have expected EOLs"""
125 files = set()
131 files = set()
126 for rev in xrange(repo[node].rev(), len(repo)):
132 for rev in xrange(repo[node].rev(), len(repo)):
127 files.update(repo[rev].files())
133 files.update(repo[rev].files())
128 tip = repo['tip']
134 tip = repo['tip']
129 for f in files:
135 for f in files:
130 if f not in tip:
136 if f not in tip:
131 continue
137 continue
132 for pattern, target in ui.configitems('encode'):
138 for pattern, target in ui.configitems('encode'):
133 if match.match(repo.root, '', [pattern])(f):
139 if match.match(repo.root, '', [pattern])(f):
134 data = tip[f].data()
140 data = tip[f].data()
135 if target == "to-lf" and "\r\n" in data:
141 if target == "to-lf" and "\r\n" in data:
136 raise util.Abort(_("%s should not have CRLF line endings")
142 raise util.Abort(_("%s should not have CRLF line endings")
137 % f)
143 % f)
138 elif target == "to-crlf" and singlelf.search(data):
144 elif target == "to-crlf" and singlelf.search(data):
139 raise util.Abort(_("%s should not have LF line endings")
145 raise util.Abort(_("%s should not have LF line endings")
140 % f)
146 % f)
141
147
142
148
143 def preupdate(ui, repo, hooktype, parent1, parent2):
149 def preupdate(ui, repo, hooktype, parent1, parent2):
144 #print "preupdate for %s: %s -> %s" % (repo.root, parent1, parent2)
150 #print "preupdate for %s: %s -> %s" % (repo.root, parent1, parent2)
145 repo.readhgeol(parent1)
151 repo.readhgeol(parent1)
146 return False
152 return False
147
153
148 def uisetup(ui):
154 def uisetup(ui):
149 ui.setconfig('hooks', 'preupdate.eol', preupdate)
155 ui.setconfig('hooks', 'preupdate.eol', preupdate)
150
156
151 def extsetup(ui):
157 def extsetup(ui):
152 try:
158 try:
153 extensions.find('win32text')
159 extensions.find('win32text')
154 raise util.Abort(_("the eol extension is incompatible with the "
160 raise util.Abort(_("the eol extension is incompatible with the "
155 "win32text extension"))
161 "win32text extension"))
156 except KeyError:
162 except KeyError:
157 pass
163 pass
158
164
159
165
160 def reposetup(ui, repo):
166 def reposetup(ui, repo):
161 uisetup(repo.ui)
167 uisetup(repo.ui)
162 #print "reposetup for", repo.root
168 #print "reposetup for", repo.root
163
169
164 if not repo.local():
170 if not repo.local():
165 return
171 return
166 for name, fn in filters.iteritems():
172 for name, fn in filters.iteritems():
167 repo.adddatafilter(name, fn)
173 repo.adddatafilter(name, fn)
168
174
169 ui.setconfig('patch', 'eol', 'auto')
175 ui.setconfig('patch', 'eol', 'auto')
170
176
171 class eolrepo(repo.__class__):
177 class eolrepo(repo.__class__):
172
178
173 _decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
179 _decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
174 _encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
180 _encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
175
181
176 def readhgeol(self, node=None, data=None):
182 def readhgeol(self, node=None, data=None):
177 if data is None:
183 if data is None:
178 try:
184 try:
179 if node is None:
185 if node is None:
180 data = self.wfile('.hgeol').read()
186 data = self.wfile('.hgeol').read()
181 else:
187 else:
182 data = self[node]['.hgeol'].data()
188 data = self[node]['.hgeol'].data()
183 except (IOError, LookupError):
189 except (IOError, LookupError):
184 return None
190 return None
185
191
186 if self.ui.config('eol', 'native', os.linesep) in ('LF', '\n'):
192 if self.ui.config('eol', 'native', os.linesep) in ('LF', '\n'):
187 self._decode['NATIVE'] = 'to-lf'
193 self._decode['NATIVE'] = 'to-lf'
188 else:
194 else:
189 self._decode['NATIVE'] = 'to-crlf'
195 self._decode['NATIVE'] = 'to-crlf'
190
196
191 eol = config.config()
197 eol = config.config()
192 # Our files should not be touched. The pattern must be
198 # Our files should not be touched. The pattern must be
193 # inserted first override a '** = native' pattern.
199 # inserted first override a '** = native' pattern.
194 eol.set('patterns', '.hg*', 'BIN')
200 eol.set('patterns', '.hg*', 'BIN')
195 # We can then parse the user's patterns.
201 # We can then parse the user's patterns.
196 eol.parse('.hgeol', data)
202 eol.parse('.hgeol', data)
197
203
198 if eol.get('repository', 'native') == 'CRLF':
204 if eol.get('repository', 'native') == 'CRLF':
199 self._encode['NATIVE'] = 'to-crlf'
205 self._encode['NATIVE'] = 'to-crlf'
200 else:
206 else:
201 self._encode['NATIVE'] = 'to-lf'
207 self._encode['NATIVE'] = 'to-lf'
202
208
203 for pattern, style in eol.items('patterns'):
209 for pattern, style in eol.items('patterns'):
204 key = style.upper()
210 key = style.upper()
205 try:
211 try:
206 self.ui.setconfig('decode', pattern, self._decode[key])
212 self.ui.setconfig('decode', pattern, self._decode[key])
207 self.ui.setconfig('encode', pattern, self._encode[key])
213 self.ui.setconfig('encode', pattern, self._encode[key])
208 except KeyError:
214 except KeyError:
209 self.ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
215 self.ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
210 % (style, eol.source('patterns', pattern)))
216 % (style, eol.source('patterns', pattern)))
211
217
212 include = []
218 include = []
213 exclude = []
219 exclude = []
214 for pattern, style in eol.items('patterns'):
220 for pattern, style in eol.items('patterns'):
215 key = style.upper()
221 key = style.upper()
216 if key == 'BIN':
222 if key == 'BIN':
217 exclude.append(pattern)
223 exclude.append(pattern)
218 else:
224 else:
219 include.append(pattern)
225 include.append(pattern)
220
226
221 # This will match the files for which we need to care
227 # This will match the files for which we need to care
222 # about inconsistent newlines.
228 # about inconsistent newlines.
223 return match.match(self.root, '', [], include, exclude)
229 return match.match(self.root, '', [], include, exclude)
224
230
225 def _hgcleardirstate(self):
231 def _hgcleardirstate(self):
226 self._eolfile = self.readhgeol() or self.readhgeol('tip')
232 self._eolfile = self.readhgeol() or self.readhgeol('tip')
227
233
228 if not self._eolfile:
234 if not self._eolfile:
229 self._eolfile = util.never
235 self._eolfile = util.never
230 return
236 return
231
237
232 try:
238 try:
233 cachemtime = os.path.getmtime(self.join("eol.cache"))
239 cachemtime = os.path.getmtime(self.join("eol.cache"))
234 except OSError:
240 except OSError:
235 cachemtime = 0
241 cachemtime = 0
236
242
237 try:
243 try:
238 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
244 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
239 except OSError:
245 except OSError:
240 eolmtime = 0
246 eolmtime = 0
241
247
242 if eolmtime > cachemtime:
248 if eolmtime > cachemtime:
243 ui.debug("eol: detected change in .hgeol\n")
249 ui.debug("eol: detected change in .hgeol\n")
244 # TODO: we could introduce a method for this in dirstate.
250 # TODO: we could introduce a method for this in dirstate.
245 wlock = None
251 wlock = None
246 try:
252 try:
247 wlock = self.wlock()
253 wlock = self.wlock()
248 for f, e in self.dirstate._map.iteritems():
254 for f, e in self.dirstate._map.iteritems():
249 self.dirstate._map[f] = (e[0], e[1], -1, 0)
255 self.dirstate._map[f] = (e[0], e[1], -1, 0)
250 self.dirstate._dirty = True
256 self.dirstate._dirty = True
251 # Touch the cache to update mtime. TODO: are we sure this
257 # Touch the cache to update mtime. TODO: are we sure this
252 # always enought to update the mtime, or should we write a
258 # always enought to update the mtime, or should we write a
253 # bit to the file?
259 # bit to the file?
254 self.opener("eol.cache", "w").close()
260 self.opener("eol.cache", "w").close()
255 finally:
261 finally:
256 if wlock is not None:
262 if wlock is not None:
257 wlock.release()
263 wlock.release()
258
264
259 def commitctx(self, ctx, error=False):
265 def commitctx(self, ctx, error=False):
260 for f in sorted(ctx.added() + ctx.modified()):
266 for f in sorted(ctx.added() + ctx.modified()):
261 if not self._eolfile(f):
267 if not self._eolfile(f):
262 continue
268 continue
263 data = ctx[f].data()
269 data = ctx[f].data()
264 if util.binary(data):
270 if util.binary(data):
265 # We should not abort here, since the user should
271 # We should not abort here, since the user should
266 # be able to say "** = native" to automatically
272 # be able to say "** = native" to automatically
267 # have all non-binary files taken care of.
273 # have all non-binary files taken care of.
268 continue
274 continue
269 if inconsistenteol(data):
275 if inconsistenteol(data):
270 raise util.Abort(_("inconsistent newline style "
276 raise util.Abort(_("inconsistent newline style "
271 "in %s\n" % f))
277 "in %s\n" % f))
272 return super(eolrepo, self).commitctx(ctx, error)
278 return super(eolrepo, self).commitctx(ctx, error)
273 repo.__class__ = eolrepo
279 repo.__class__ = eolrepo
274 repo._hgcleardirstate()
280 repo._hgcleardirstate()
@@ -1,141 +1,160 b''
1 $ echo "[extensions]" >> $HGRCPATH
1 $ echo "[extensions]" >> $HGRCPATH
2 $ echo "churn=" >> $HGRCPATH
2 $ echo "churn=" >> $HGRCPATH
3
3
4 create test repository
4 create test repository
5
5
6 $ hg init repo
6 $ hg init repo
7 $ cd repo
7 $ cd repo
8 $ echo a > a
8 $ echo a > a
9 $ hg ci -Am adda -u user1 -d 6:00
9 $ hg ci -Am adda -u user1 -d 6:00
10 adding a
10 adding a
11 $ echo b >> a
11 $ echo b >> a
12 $ echo b > b
12 $ echo b > b
13 $ hg ci -m changeba -u user2 -d 9:00 a
13 $ hg ci -m changeba -u user2 -d 9:00 a
14 $ hg ci -Am addb -u user2 -d 9:30
14 $ hg ci -Am addb -u user2 -d 9:30
15 adding b
15 adding b
16 $ echo c >> a
16 $ echo c >> a
17 $ echo c >> b
17 $ echo c >> b
18 $ echo c > c
18 $ echo c > c
19 $ hg ci -m changeca -u user3 -d 12:00 a
19 $ hg ci -m changeca -u user3 -d 12:00 a
20 $ hg ci -m changecb -u user3 -d 12:15 b
20 $ hg ci -m changecb -u user3 -d 12:15 b
21 $ hg ci -Am addc -u user3 -d 12:30
21 $ hg ci -Am addc -u user3 -d 12:30
22 adding c
22 adding c
23 $ mkdir -p d/e
23 $ mkdir -p d/e
24 $ echo abc > d/e/f1.txt
24 $ echo abc > d/e/f1.txt
25 $ hg ci -Am "add d/e/f1.txt" -u user1 -d 12:45 d/e/f1.txt
25 $ hg ci -Am "add d/e/f1.txt" -u user1 -d 12:45 d/e/f1.txt
26 $ mkdir -p d/g
26 $ mkdir -p d/g
27 $ echo def > d/g/f2.txt
27 $ echo def > d/g/f2.txt
28 $ hg ci -Am "add d/g/f2.txt" -u user1 -d 13:00 d/g/f2.txt
28 $ hg ci -Am "add d/g/f2.txt" -u user1 -d 13:00 d/g/f2.txt
29
29
30
30
31 churn separate directories
31 churn separate directories
32
32
33 $ cd d
33 $ cd d
34 $ hg churn e
34 $ hg churn e
35 user1 1 ***************************************************************
35 user1 1 ***************************************************************
36
36
37 churn all
37 churn all
38
38
39 $ hg churn
39 $ hg churn
40 user3 3 ***************************************************************
40 user3 3 ***************************************************************
41 user1 3 ***************************************************************
41 user1 3 ***************************************************************
42 user2 2 ******************************************
42 user2 2 ******************************************
43
43
44 churn excluding one dir
44 churn excluding one dir
45
45
46 $ hg churn -X e
46 $ hg churn -X e
47 user3 3 ***************************************************************
47 user3 3 ***************************************************************
48 user2 2 ******************************************
48 user2 2 ******************************************
49 user1 2 ******************************************
49 user1 2 ******************************************
50
50
51 churn up to rev 2
51 churn up to rev 2
52
52
53 $ hg churn -r :2
53 $ hg churn -r :2
54 user2 2 ***************************************************************
54 user2 2 ***************************************************************
55 user1 1 ********************************
55 user1 1 ********************************
56 $ cd ..
56 $ cd ..
57
57
58 churn with aliases
58 churn with aliases
59
59
60 $ cat > ../aliases <<EOF
60 $ cat > ../aliases <<EOF
61 > user1 alias1
61 > user1 alias1
62 > user3 alias3
62 > user3 alias3
63 > not-an-alias
63 > not-an-alias
64 > EOF
64 > EOF
65
65
66 churn with .hgchurn
66 churn with .hgchurn
67
67
68 $ mv ../aliases .hgchurn
68 $ mv ../aliases .hgchurn
69 $ hg churn
69 $ hg churn
70 skipping malformed alias: not-an-alias
70 skipping malformed alias: not-an-alias
71 alias3 3 **************************************************************
71 alias3 3 **************************************************************
72 alias1 3 **************************************************************
72 alias1 3 **************************************************************
73 user2 2 *****************************************
73 user2 2 *****************************************
74 $ rm .hgchurn
74 $ rm .hgchurn
75
75
76 churn with column specifier
76 churn with column specifier
77
77
78 $ COLUMNS=40 hg churn
78 $ COLUMNS=40 hg churn
79 user3 3 ***********************
79 user3 3 ***********************
80 user1 3 ***********************
80 user1 3 ***********************
81 user2 2 ***************
81 user2 2 ***************
82
82
83 churn by hour
83 churn by hour
84
84
85 $ hg churn -f '%H' -s
85 $ hg churn -f '%H' -s
86 06 1 *****************
86 06 1 *****************
87 09 2 *********************************
87 09 2 *********************************
88 12 4 ******************************************************************
88 12 4 ******************************************************************
89 13 1 *****************
89 13 1 *****************
90
90
91
91
92 churn with separated added/removed lines
92 churn with separated added/removed lines
93
93
94 $ hg rm d/g/f2.txt
94 $ hg rm d/g/f2.txt
95 $ hg ci -Am "removed d/g/f2.txt" -u user1 -d 14:00 d/g/f2.txt
95 $ hg ci -Am "removed d/g/f2.txt" -u user1 -d 14:00 d/g/f2.txt
96 $ hg churn --diffstat
96 $ hg churn --diffstat
97 user1 +3/-1 +++++++++++++++++++++++++++++++++++++++++--------------
97 user1 +3/-1 +++++++++++++++++++++++++++++++++++++++++--------------
98 user3 +3/-0 +++++++++++++++++++++++++++++++++++++++++
98 user3 +3/-0 +++++++++++++++++++++++++++++++++++++++++
99 user2 +2/-0 +++++++++++++++++++++++++++
99 user2 +2/-0 +++++++++++++++++++++++++++
100
100
101 churn --diffstat with color
101 churn --diffstat with color
102
102
103 $ hg --config extensions.color= churn --config color.mode=ansi \
103 $ hg --config extensions.color= churn --config color.mode=ansi \
104 > --diffstat --color=always
104 > --diffstat --color=always
105 user1 +3/-1 \x1b[0;32m+++++++++++++++++++++++++++++++++++++++++\x1b[0m\x1b[0;31m--------------\x1b[0m (esc)
105 user1 +3/-1 \x1b[0;32m+++++++++++++++++++++++++++++++++++++++++\x1b[0m\x1b[0;31m--------------\x1b[0m (esc)
106 user3 +3/-0 \x1b[0;32m+++++++++++++++++++++++++++++++++++++++++\x1b[0m (esc)
106 user3 +3/-0 \x1b[0;32m+++++++++++++++++++++++++++++++++++++++++\x1b[0m (esc)
107 user2 +2/-0 \x1b[0;32m+++++++++++++++++++++++++++\x1b[0m (esc)
107 user2 +2/-0 \x1b[0;32m+++++++++++++++++++++++++++\x1b[0m (esc)
108
108
109
109
110 changeset number churn
110 changeset number churn
111
111
112 $ hg churn -c
112 $ hg churn -c
113 user1 4 ***************************************************************
113 user1 4 ***************************************************************
114 user3 3 ***********************************************
114 user3 3 ***********************************************
115 user2 2 ********************************
115 user2 2 ********************************
116
116
117 $ echo 'with space = no-space' >> ../aliases
117 $ echo 'with space = no-space' >> ../aliases
118 $ echo a >> a
118 $ echo a >> a
119 $ hg commit -m a -u 'with space' -d 15:00
119 $ hg commit -m a -u 'with space' -d 15:00
120
120
121 churn with space in alias
121 churn with space in alias
122
122
123 $ hg churn --aliases ../aliases -r tip
123 $ hg churn --aliases ../aliases -r tip
124 no-space 1 ************************************************************
124 no-space 1 ************************************************************
125
125
126 $ cd ..
126 $ cd ..
127
127
128
128
129 Issue833: ZeroDivisionError
129 Issue833: ZeroDivisionError
130
130
131 $ hg init issue-833
131 $ hg init issue-833
132 $ cd issue-833
132 $ cd issue-833
133 $ touch foo
133 $ touch foo
134 $ hg ci -Am foo
134 $ hg ci -Am foo
135 adding foo
135 adding foo
136
136
137 this was failing with a ZeroDivisionError
137 this was failing with a ZeroDivisionError
138
138
139 $ hg churn
139 $ hg churn
140 test 0
140 test 0
141 $ cd ..
141 $ cd ..
142
143 Ignore trailing or leading spaces in emails
144
145 $ cd repo
146 $ touch bar
147 $ hg ci -Am'bar' -u 'user4 <user4@x.com>'
148 adding bar
149 $ touch foo
150 $ hg ci -Am'foo' -u 'user4 < user4@x.com >'
151 adding foo
152 $ hg log -l2 --template '[{author|email}]\n'
153 [ user4@x.com ]
154 [user4@x.com]
155 $ hg churn -c
156 user1 4 *********************************************************
157 user3 3 *******************************************
158 user4@x.com 2 *****************************
159 user2 2 *****************************
160 with space 1 **************
General Comments 0
You need to be logged in to leave comments. Login now