##// END OF EJS Templates
merge.
Vadim Gelfer -
r2956:6dddcba7 merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,99
1 # fetch.py - pull and merge remote changes
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
7
8 from mercurial.demandload import *
9 from mercurial.i18n import gettext as _
10 from mercurial.node import *
11 demandload(globals(), 'mercurial:commands,hg,node,util')
12
13 def fetch(ui, repo, source='default', **opts):
14 '''Pull changes from a remote repository, merge new changes if needed.
15
16 This finds all changes from the repository at the specified path
17 or URL and adds them to the local repository.
18
19 If the pulled changes add a new head, the head is automatically
20 merged, and the result of the merge is committed. Otherwise, the
21 working directory is updated.'''
22
23 def postincoming(other, modheads):
24 if modheads == 0:
25 return 0
26 if modheads == 1:
27 return hg.clean(repo, repo.changelog.tip(), wlock=wlock)
28 newheads = repo.heads(parent)
29 newchildren = [n for n in repo.heads(parent) if n != parent]
30 newparent = parent
31 if newchildren:
32 newparent = newchildren[0]
33 hg.clean(repo, newparent, wlock=wlock)
34 newheads = [n for n in repo.heads() if n != newparent]
35 err = False
36 if newheads:
37 ui.status(_('merging with new head %d:%s\n') %
38 (repo.changelog.rev(newheads[0]), short(newheads[0])))
39 err = hg.merge(repo, newheads[0], remind=False, wlock=wlock)
40 if not err and len(newheads) > 1:
41 ui.status(_('not merging with %d other new heads '
42 '(use "hg heads" and "hg merge" to merge them)') %
43 (len(newheads) - 1))
44 if not err:
45 mod, add, rem = repo.status(wlock=wlock)[:3]
46 message = (commands.logmessage(opts) or
47 (_('Automated merge with %s') % other.url()))
48 n = repo.commit(mod + add + rem, message,
49 opts['user'], opts['date'], lock=lock, wlock=wlock,
50 force_editor=opts.get('force_editor'))
51 ui.status(_('new changeset %d:%s merges remote changes '
52 'with local\n') % (repo.changelog.rev(n),
53 short(n)))
54 def pull():
55 commands.setremoteconfig(ui, opts)
56
57 other = hg.repository(ui, ui.expandpath(source))
58 ui.status(_('pulling from %s\n') % ui.expandpath(source))
59 revs = None
60 if opts['rev'] and not other.local():
61 raise util.Abort(_("fetch -r doesn't work for remote repositories yet"))
62 elif opts['rev']:
63 revs = [other.lookup(rev) for rev in opts['rev']]
64 modheads = repo.pull(other, heads=revs, lock=lock)
65 return postincoming(other, modheads)
66
67 parent, p2 = repo.dirstate.parents()
68 if parent != repo.changelog.tip():
69 raise util.Abort(_('working dir not at tip '
70 '(use "hg update" to check out tip)'))
71 if p2 != nullid:
72 raise util.Abort(_('outstanding uncommitted merge'))
73 wlock = repo.wlock()
74 lock = repo.lock()
75 try:
76 mod, add, rem = repo.status(wlock=wlock)[:3]
77 if mod or add or rem:
78 raise util.Abort(_('outstanding uncommitted changes'))
79 if len(repo.heads()) > 1:
80 raise util.Abort(_('multiple heads in this repository '
81 '(use "hg heads" and "hg merge" to merge)'))
82 return pull()
83 finally:
84 lock.release()
85 wlock.release()
86
87 cmdtable = {
88 'fetch':
89 (fetch,
90 [('e', 'ssh', '', _('specify ssh command to use')),
91 ('m', 'message', '', _('use <text> as commit message')),
92 ('l', 'logfile', '', _('read the commit message from <file>')),
93 ('d', 'date', '', _('record datecode as commit date')),
94 ('u', 'user', '', _('record user as commiter')),
95 ('r', 'rev', [], _('a specific revision you would like to pull')),
96 ('f', 'force-editor', None, _('edit commit message')),
97 ('', 'remotecmd', '', _('hg command to run on the remote side'))],
98 'hg fetch [SOURCE]'),
99 }
@@ -0,0 +1,111
1 # commands.py - command processing for mercurial
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
7
8 from demandload import demandload
9 from node import *
10 from i18n import gettext as _
11 demandload(globals(), 'util')
12 demandload(globals(), 'os sys')
13
14 def make_filename(repo, pat, node,
15 total=None, seqno=None, revwidth=None, pathname=None):
16 node_expander = {
17 'H': lambda: hex(node),
18 'R': lambda: str(repo.changelog.rev(node)),
19 'h': lambda: short(node),
20 }
21 expander = {
22 '%': lambda: '%',
23 'b': lambda: os.path.basename(repo.root),
24 }
25
26 try:
27 if node:
28 expander.update(node_expander)
29 if node and revwidth is not None:
30 expander['r'] = (lambda:
31 str(repo.changelog.rev(node)).zfill(revwidth))
32 if total is not None:
33 expander['N'] = lambda: str(total)
34 if seqno is not None:
35 expander['n'] = lambda: str(seqno)
36 if total is not None and seqno is not None:
37 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
38 if pathname is not None:
39 expander['s'] = lambda: os.path.basename(pathname)
40 expander['d'] = lambda: os.path.dirname(pathname) or '.'
41 expander['p'] = lambda: pathname
42
43 newname = []
44 patlen = len(pat)
45 i = 0
46 while i < patlen:
47 c = pat[i]
48 if c == '%':
49 i += 1
50 c = pat[i]
51 c = expander[c]()
52 newname.append(c)
53 i += 1
54 return ''.join(newname)
55 except KeyError, inst:
56 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
57 inst.args[0])
58
59 def make_file(repo, pat, node=None,
60 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
61 if not pat or pat == '-':
62 return 'w' in mode and sys.stdout or sys.stdin
63 if hasattr(pat, 'write') and 'w' in mode:
64 return pat
65 if hasattr(pat, 'read') and 'r' in mode:
66 return pat
67 return open(make_filename(repo, pat, node, total, seqno, revwidth,
68 pathname),
69 mode)
70
71 def matchpats(repo, pats=[], opts={}, head=''):
72 cwd = repo.getcwd()
73 if not pats and cwd:
74 opts['include'] = [os.path.join(cwd, i)
75 for i in opts.get('include', [])]
76 opts['exclude'] = [os.path.join(cwd, x)
77 for x in opts.get('exclude', [])]
78 cwd = ''
79 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
80 opts.get('exclude'), head)
81
82 def makewalk(repo, pats=[], opts={}, node=None, head='', badmatch=None):
83 files, matchfn, anypats = matchpats(repo, pats, opts, head)
84 exact = dict(zip(files, files))
85 def walk():
86 for src, fn in repo.walk(node=node, files=files, match=matchfn,
87 badmatch=badmatch):
88 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
89 return files, matchfn, walk()
90
91 def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None):
92 files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
93 for r in results:
94 yield r
95
96 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None):
97 if dry_run is None:
98 dry_run = opts.get('dry_run')
99 add, remove = [], []
100 for src, abs, rel, exact in walk(repo, pats, opts):
101 if src == 'f' and repo.dirstate.state(abs) == '?':
102 add.append(abs)
103 if repo.ui.verbose or not exact:
104 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
105 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
106 remove.append(abs)
107 if repo.ui.verbose or not exact:
108 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
109 if not dry_run:
110 repo.add(add, wlock=wlock)
111 repo.remove(remove, wlock=wlock)
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,1 +1,2
1 1 35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0 iD8DBQBEYmO2ywK+sNU5EO8RAnaYAKCO7x15xUn5mnhqWNXqk/ehlhRt2QCfRDfY0LrUq2q4oK/KypuJYPHgq1A=
2 2be3001847cb18a23c403439d9e7d0ace30804e9 0 iD8DBQBExUbjywK+sNU5EO8RAhzxAKCtyHAQUzcTSZTqlfJ0by6vhREwWQCghaQFHfkfN0l9/40EowNhuMOKnJk=
@@ -1,13 +1,14
1 1 d40cc5aacc31ed673d9b5b24f98bee78c283062c 0.4f
2 2 1c590d34bf61e2ea12c71738e5a746cd74586157 0.4e
3 3 7eca4cfa8aad5fce9a04f7d8acadcd0452e2f34e 0.4d
4 4 b4d0c3786ad3e47beacf8412157326a32b6d25a4 0.4c
5 5 f40273b0ad7b3a6d3012fd37736d0611f41ecf54 0.5
6 6 0a28dfe59f8fab54a5118c5be4f40da34a53cdb7 0.5b
7 7 12e0fdbc57a0be78f0e817fd1d170a3615cd35da 0.6
8 8 4ccf3de52989b14c3d84e1097f59e39a992e00bd 0.6b
9 9 eac9c8efcd9bd8244e72fb6821f769f450457a32 0.6c
10 10 979c049974485125e1f9357f6bbe9c1b548a64c3 0.7
11 11 3a56574f329a368d645853e0f9e09472aee62349 0.8
12 12 6a03cff2b0f5d30281e6addefe96b993582f2eac 0.8.1
13 13 35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0.9
14 2be3001847cb18a23c403439d9e7d0ace30804e9 0.9.1
@@ -1,39 +1,40
1 1 Andrea Arcangeli <andrea at suse.de>
2 2 Thomas Arendsen Hein <thomas at intevation.de>
3 3 Goffredo Baroncelli <kreijack at libero.it>
4 4 Muli Ben-Yehuda <mulix at mulix.org>
5 5 Mikael Berthe <mikael at lilotux.net>
6 6 Benoit Boissinot <bboissin at gmail.com>
7 Brendan Cully <brendan at kublai.com>
7 8 Vincent Danjean <vdanjean.ml at free.fr>
8 9 Jake Edge <jake at edge2.net>
9 10 Michael Fetterman <michael.fetterman at intel.com>
10 11 Edouard Gomez <ed.gomez at free.fr>
11 12 Eric Hopper <hopper at omnifarious.org>
12 13 Alecs King <alecsk at gmail.com>
13 14 Volker Kleinfeld <Volker.Kleinfeld at gmx.de>
14 15 Vadim Lebedev <vadim at mbdsys.com>
15 16 Christopher Li <hg at chrisli.org>
16 17 Chris Mason <mason at suse.com>
17 18 Colin McMillen <mcmillen at cs.cmu.edu>
18 19 Wojciech Milkowski <wmilkowski at interia.pl>
19 20 Chad Netzer <chad.netzer at gmail.com>
20 21 Bryan O'Sullivan <bos at serpentine.com>
21 22 Vicent Seguí Pascual <vseguip at gmail.com>
22 23 Sean Perry <shaleh at speakeasy.net>
23 24 Nguyen Anh Quynh <aquynh at gmail.com>
24 25 Ollivier Robert <roberto at keltia.freenix.fr>
25 26 Alexander Schremmer <alex at alexanderweb.de>
26 27 Arun Sharma <arun at sharma-home.net>
27 28 Josef "Jeff" Sipek <jeffpc at optonline.net>
28 29 Kevin Smith <yarcs at qualitycode.com>
29 30 TK Soh <teekaysoh at yahoo.com>
30 31 Radoslaw Szkodzinski <astralstorm at gorzow.mm.pl>
31 32 Samuel Tardieu <sam at rfc1149.net>
32 33 K Thananchayan <thananck at yahoo.com>
33 34 Andrew Thompson <andrewkt at aktzero.com>
34 35 Michael S. Tsirkin <mst at mellanox.co.il>
35 36 Rafael Villar Burke <pachi at mmn-arquitectos.com>
36 37 Tristan Wibberley <tristan at wibberley.org>
37 38 Mark Williamson <mark.williamson at cl.cam.ac.uk>
38 39
39 40 If you are a contributor and don't see your name here, please let me know.
@@ -1,17 +1,19
1 1 include hg
2 2 recursive-include mercurial *.py
3 3 include hgweb.cgi hgwebdir.cgi
4 4 include hgeditor rewrite-log
5 include tests/README tests/coverage.py tests/run-tests.py tests/md5sum.py tests/test-*[a-z0-9] tests/*.out
5 include tests/README tests/*.py tests/test-*[a-z0-9] tests/*.out
6 6 prune tests/*.err
7 7 include *.txt
8 8 include templates/map templates/map-*[a-z0-9]
9 9 include templates/*.tmpl
10 10 include templates/static/*
11 11 include doc/README doc/Makefile doc/gendoc.py doc/*.txt doc/*.html doc/*.[0-9]
12 12 recursive-include contrib *
13 recursive-include hgext *
13 14 include README
14 15 include CONTRIBUTORS
15 16 include COPYING
16 17 include Makefile
17 18 include MANIFEST.in
19 prune *.elc *.orig *.rej *~ *.o *.so *.pyc *.swp *.prof
@@ -1,385 +1,390
1 1 # bash completion for the Mercurial distributed SCM
2 2
3 3 # Docs:
4 4 #
5 5 # If you source this file from your .bashrc, bash should be able to
6 6 # complete a command line that uses hg with all the available commands
7 7 # and options and sometimes even arguments.
8 8 #
9 9 # Mercurial allows you to define additional commands through extensions.
10 10 # Bash should be able to automatically figure out the name of these new
11 11 # commands and their options. If you also want to tell it how to
12 12 # complete non-option arguments, see below for how to define an
13 13 # _hg_cmd_foo function.
14 14 #
15 15 #
16 16 # Notes about completion for specific commands:
17 17 #
18 18 # - the completion function for the email command from the patchbomb
19 19 # extension will try to call _hg_emails to get a list of e-mail
20 20 # addresses. It's up to the user to define this function. For
21 21 # example, put the addresses of the lists that you usually patchbomb
22 22 # in ~/.patchbomb-to and the addresses that you usually use to send
23 23 # the patchbombs in ~/.patchbomb-from and use something like this:
24 24 #
25 25 # _hg_emails()
26 26 # {
27 27 # if [ -r ~/.patchbomb-$1 ]; then
28 28 # cat ~/.patchbomb-$1
29 29 # fi
30 30 # }
31 31 #
32 32 #
33 33 # Writing completion functions for additional commands:
34 34 #
35 35 # If it exists, the function _hg_cmd_foo will be called without
36 36 # arguments to generate the completion candidates for the hg command
37 37 # "foo".
38 38 #
39 39 # In addition to the regular completion variables provided by bash,
40 40 # the following variables are also set:
41 41 # - $hg - the hg program being used (e.g. /usr/bin/hg)
42 42 # - $cmd - the name of the hg command being completed
43 43 # - $cmd_index - the index of $cmd in $COMP_WORDS
44 44 # - $cur - the current argument being completed
45 45 # - $prev - the argument before $cur
46 46 # - $global_args - "|"-separated list of global options that accept
47 47 # an argument (e.g. '--cwd|-R|--repository')
48 48 # - $canonical - 1 if we canonicalized $cmd before calling the function
49 49 # 0 otherwise
50 50 #
51 51
52 52 shopt -s extglob
53 53
54 54 _hg_commands()
55 55 {
56 56 local commands
57 57 commands="$("$hg" debugcomplete "$cur" 2>/dev/null)" || commands=""
58 58 COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$commands' -- "$cur"))
59 59 }
60 60
61 61 _hg_paths()
62 62 {
63 63 local paths="$("$hg" paths 2>/dev/null | sed -e 's/ = .*$//')"
64 64 COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$paths' -- "$cur"))
65 65 }
66 66
67 67 _hg_repos()
68 68 {
69 69 local i
70 70 for i in $(compgen -d -- "$cur"); do
71 71 test ! -d "$i"/.hg || COMPREPLY=(${COMPREPLY[@]:-} "$i")
72 72 done
73 73 }
74 74
75 75 _hg_status()
76 76 {
77 77 local files="$("$hg" status -n$1 . 2>/dev/null)"
78 78 COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$files' -- "$cur"))
79 79 }
80 80
81 81 _hg_tags()
82 82 {
83 83 local tags="$("$hg" tags -q 2>/dev/null)"
84 84 local IFS=$'\n'
85 85 COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$tags' -- "$cur"))
86 86 }
87 87
88 88 # this is "kind of" ugly...
89 89 _hg_count_non_option()
90 90 {
91 91 local i count=0
92 92 local filters="$1"
93 93
94 94 for ((i=1; $i<=$COMP_CWORD; i++)); do
95 95 if [[ "${COMP_WORDS[i]}" != -* ]]; then
96 96 if [[ ${COMP_WORDS[i-1]} == @($filters|$global_args) ]]; then
97 97 continue
98 98 fi
99 99 count=$(($count + 1))
100 100 fi
101 101 done
102 102
103 103 echo $(($count - 1))
104 104 }
105 105
106 106 _hg()
107 107 {
108 108 local cur prev cmd cmd_index opts i
109 109 # global options that receive an argument
110 110 local global_args='--cwd|-R|--repository'
111 111 local hg="$1"
112 112
113 113 COMPREPLY=()
114 114 cur="$2"
115 115 prev="$3"
116 116
117 117 # searching for the command
118 118 # (first non-option argument that doesn't follow a global option that
119 119 # receives an argument)
120 120 for ((i=1; $i<=$COMP_CWORD; i++)); do
121 121 if [[ ${COMP_WORDS[i]} != -* ]]; then
122 122 if [[ ${COMP_WORDS[i-1]} != @($global_args) ]]; then
123 123 cmd="${COMP_WORDS[i]}"
124 124 cmd_index=$i
125 125 break
126 126 fi
127 127 fi
128 128 done
129 129
130 130 if [[ "$cur" == -* ]]; then
131 131 opts=$("$hg" debugcomplete --options "$cmd" 2>/dev/null)
132 132
133 133 COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$opts' -- "$cur"))
134 134 return
135 135 fi
136 136
137 137 # global options
138 138 case "$prev" in
139 139 -R|--repository)
140 140 _hg_repos
141 141 return
142 142 ;;
143 143 --cwd)
144 144 # Stick with default bash completion
145 145 return
146 146 ;;
147 147 esac
148 148
149 149 if [ -z "$cmd" ] || [ $COMP_CWORD -eq $i ]; then
150 150 _hg_commands
151 151 return
152 152 fi
153 153
154 154 # try to generate completion candidates for whatever command the user typed
155 155 local help
156 156 local canonical=0
157 157 if _hg_command_specific; then
158 158 return
159 159 fi
160 160
161 161 # canonicalize the command name and try again
162 162 help=$("$hg" help "$cmd" 2>/dev/null)
163 163 if [ $? -ne 0 ]; then
164 164 # Probably either the command doesn't exist or it's ambiguous
165 165 return
166 166 fi
167 167 cmd=${help#hg }
168 168 cmd=${cmd%%[$' \n']*}
169 169 canonical=1
170 170 _hg_command_specific
171 171 }
172 172
173 173 _hg_command_specific()
174 174 {
175 175 if [ "$(type -t "_hg_cmd_$cmd")" = function ]; then
176 176 "_hg_cmd_$cmd"
177 177 return 0
178 178 fi
179 179
180 180 if [ "$cmd" != status ] && [ "$prev" = -r ] || [ "$prev" == --rev ]; then
181 181 if [ $canonical = 1 ]; then
182 182 _hg_tags
183 183 return 0
184 184 elif [[ status != "$cmd"* ]]; then
185 185 _hg_tags
186 186 return 0
187 187 else
188 188 return 1
189 189 fi
190 190 fi
191 191
192 192 case "$cmd" in
193 193 help)
194 194 _hg_commands
195 195 ;;
196 196 export|manifest|update)
197 197 _hg_tags
198 198 ;;
199 199 pull|push|outgoing|incoming)
200 200 _hg_paths
201 201 _hg_repos
202 202 ;;
203 203 paths)
204 204 _hg_paths
205 205 ;;
206 206 add)
207 207 _hg_status "u"
208 208 ;;
209 209 commit)
210 210 _hg_status "mar"
211 211 ;;
212 212 remove)
213 213 _hg_status "d"
214 214 ;;
215 215 forget)
216 216 _hg_status "a"
217 217 ;;
218 218 diff)
219 219 _hg_status "mar"
220 220 ;;
221 221 revert)
222 222 _hg_status "mard"
223 223 ;;
224 224 clone)
225 225 local count=$(_hg_count_non_option)
226 226 if [ $count = 1 ]; then
227 227 _hg_paths
228 228 fi
229 229 _hg_repos
230 230 ;;
231 231 debugindex|debugindexdot)
232 232 COMPREPLY=(${COMPREPLY[@]:-} $(compgen -f -X "!*.i" -- "$cur"))
233 233 ;;
234 234 debugdata)
235 235 COMPREPLY=(${COMPREPLY[@]:-} $(compgen -f -X "!*.d" -- "$cur"))
236 236 ;;
237 237 *)
238 238 return 1
239 239 ;;
240 240 esac
241 241
242 242 return 0
243 243 }
244 244
245 245 complete -o bashdefault -o default -F _hg hg 2>/dev/null \
246 246 || complete -o default -F _hg hg
247 247
248 248
249 249 # Completion for commands provided by extensions
250 250
251 251 # mq
252 252 _hg_ext_mq_patchlist()
253 253 {
254 254 local patches=$("$hg" $1 2>/dev/null)
255 255 COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$patches' -- "$cur"))
256 256 }
257 257
258 258 _hg_ext_mq_queues()
259 259 {
260 260 local root=$("$hg" root 2>/dev/null)
261 261 local n
262 262 for n in $(cd "$root"/.hg && compgen -d -- "$cur"); do
263 263 # I think we're usually not interested in the regular "patches" queue
264 264 # so just filter it.
265 265 if [ "$n" != patches ] && [ -e "$root/.hg/$n/series" ]; then
266 266 COMPREPLY=(${COMPREPLY[@]:-} "$n")
267 267 fi
268 268 done
269 269 }
270 270
271 271 _hg_cmd_qpop()
272 272 {
273 273 if [[ "$prev" = @(-n|--name) ]]; then
274 274 _hg_ext_mq_queues
275 275 return
276 276 fi
277 277 _hg_ext_mq_patchlist qapplied
278 278 }
279 279
280 280 _hg_cmd_qpush()
281 281 {
282 282 if [[ "$prev" = @(-n|--name) ]]; then
283 283 _hg_ext_mq_queues
284 284 return
285 285 fi
286 286 _hg_ext_mq_patchlist qunapplied
287 287 }
288 288
289 289 _hg_cmd_qdelete()
290 290 {
291 _hg_ext_mq_patchlist qseries
291 _hg_ext_mq_patchlist qunapplied
292 292 }
293 293
294 294 _hg_cmd_qsave()
295 295 {
296 296 if [[ "$prev" = @(-n|--name) ]]; then
297 297 _hg_ext_mq_queues
298 298 return
299 299 fi
300 300 }
301 301
302 302 _hg_cmd_strip()
303 303 {
304 304 _hg_tags
305 305 }
306 306
307 307 _hg_cmd_qcommit()
308 308 {
309 309 local root=$("$hg" root 2>/dev/null)
310 310 # this is run in a sub-shell, so we can't use _hg_status
311 311 local files=$(cd "$root/.hg/patches" 2>/dev/null &&
312 312 "$hg" status -nmar 2>/dev/null)
313 313 COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$files' -- "$cur"))
314 314 }
315 315
316 _hg_cmd_export()
317 {
318 _hg_ext_mq_patchlist qapplied
319 }
320
316 321
317 322 # hbisect
318 323 _hg_cmd_bisect()
319 324 {
320 325 local i subcmd
321 326
322 327 # find the sub-command
323 328 for ((i=cmd_index+1; i<=COMP_CWORD; i++)); do
324 329 if [[ ${COMP_WORDS[i]} != -* ]]; then
325 330 if [[ ${COMP_WORDS[i-1]} != @($global_args) ]]; then
326 331 subcmd="${COMP_WORDS[i]}"
327 332 break
328 333 fi
329 334 fi
330 335 done
331 336
332 337 if [ -z "$subcmd" ] || [ $COMP_CWORD -eq $i ] || [ "$subcmd" = help ]; then
333 338 COMPREPLY=(${COMPREPLY[@]:-}
334 339 $(compgen -W 'bad good help init next reset' -- "$cur"))
335 340 return
336 341 fi
337 342
338 343 case "$subcmd" in
339 344 good|bad)
340 345 _hg_tags
341 346 ;;
342 347 esac
343 348
344 349 return
345 350 }
346 351
347 352
348 353 # patchbomb
349 354 _hg_cmd_email()
350 355 {
351 356 case "$prev" in
352 357 -c|--cc|-t|--to|-f|--from)
353 358 # we need an e-mail address. let the user provide a function
354 359 # to get them
355 360 if [ "$(type -t _hg_emails)" = function ]; then
356 361 local arg=to
357 362 if [[ "$prev" == @(-f|--from) ]]; then
358 363 arg=from
359 364 fi
360 365 local addresses=$(_hg_emails $arg)
361 366 COMPREPLY=(${COMPREPLY[@]:-}
362 367 $(compgen -W '$addresses' -- "$cur"))
363 368 fi
364 369 return
365 370 ;;
366 371 -m|--mbox)
367 372 # fallback to standard filename completion
368 373 return
369 374 ;;
370 375 -s|--subject)
371 376 # free form string
372 377 return
373 378 ;;
374 379 esac
375 380
376 381 _hg_tags
377 382 return
378 383 }
379 384
380 385
381 386 # gpg
382 387 _hg_cmd_sign()
383 388 {
384 389 _hg_tags
385 390 }
@@ -1,289 +1,290
1 1 #!/usr/bin/env python
2 2 #
3 3 # This is a generalized framework for converting between SCM
4 4 # repository formats.
5 5 #
6 6 # In its current form, it's hardcoded to convert incrementally between
7 7 # git and Mercurial.
8 8 #
9 9 # To use, you must first import the first git version into Mercurial,
10 10 # and establish a mapping between the git commit hash and the hash in
11 11 # Mercurial for that version. This mapping is kept in a simple text
12 12 # file with lines like so:
13 13 #
14 14 # <git hash> <mercurial hash>
15 15 #
16 16 # To convert the rest of the repo, run:
17 17 #
18 18 # convert-repo <git-dir> <hg-dir> <mapfile>
19 19 #
20 20 # This updates the mapfile on each commit copied, so it can be
21 21 # interrupted and can be run repeatedly to copy new commits.
22 22
23 23 import sys, os, zlib, sha, time
24 24 from mercurial import hg, ui, util
25 25
26 26 class convert_git:
27 27 def __init__(self, path):
28 28 self.path = path
29 29
30 30 def getheads(self):
31 return [file(self.path + "/HEAD").read()[:-1]]
31 fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path)
32 return [fh.read()[:-1]]
32 33
33 34 def catfile(self, rev, type):
34 35 if rev == "0" * 40: raise IOError()
35 36 fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null" % (self.path, type, rev))
36 37 return fh.read()
37 38
38 39 def getfile(self, name, rev):
39 40 return self.catfile(rev, "blob")
40 41
41 42 def getchanges(self, version):
42 43 fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s" % (self.path, version))
43 44 changes = []
44 45 for l in fh:
45 46 if "\t" not in l: continue
46 47 m, f = l[:-1].split("\t")
47 48 m = m.split()
48 49 h = m[3]
49 50 p = (m[1] == "100755")
50 51 changes.append((f, h, p))
51 52 return changes
52 53
53 54 def getcommit(self, version):
54 55 c = self.catfile(version, "commit") # read the commit hash
55 56 end = c.find("\n\n")
56 57 message = c[end+2:]
57 58 l = c[:end].splitlines()
58 59 manifest = l[0].split()[1]
59 60 parents = []
60 61 for e in l[1:]:
61 62 n,v = e.split(" ", 1)
62 63 if n == "author":
63 64 p = v.split()
64 65 tm, tz = p[-2:]
65 66 author = " ".join(p[:-2])
66 67 if author[0] == "<": author = author[1:-1]
67 68 if n == "committer":
68 69 p = v.split()
69 70 tm, tz = p[-2:]
70 71 committer = " ".join(p[:-2])
71 72 if committer[0] == "<": committer = committer[1:-1]
72 73 message += "\ncommitter: %s\n" % v
73 74 if n == "parent": parents.append(v)
74 75
75 76 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
76 77 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
77 78 date = tm + " " + str(tz)
78 79 return (parents, author, date, message)
79 80
80 81 def gettags(self):
81 82 tags = {}
82 83 for f in os.listdir(self.path + "/refs/tags"):
83 84 try:
84 85 h = file(self.path + "/refs/tags/" + f).read().strip()
85 86 c = self.catfile(h, "tag") # read the commit hash
86 87 h = c.splitlines()[0].split()[1]
87 88 tags[f] = h
88 89 except:
89 90 pass
90 91 return tags
91 92
92 93 class convert_mercurial:
93 94 def __init__(self, path):
94 95 self.path = path
95 96 u = ui.ui()
96 97 self.repo = hg.repository(u, path)
97 98
98 99 def getheads(self):
99 100 h = self.repo.changelog.heads()
100 101 return [ hg.hex(x) for x in h ]
101 102
102 103 def putfile(self, f, e, data):
103 104 self.repo.wfile(f, "w").write(data)
104 105 if self.repo.dirstate.state(f) == '?':
105 106 self.repo.dirstate.update([f], "a")
106 107
107 108 util.set_exec(self.repo.wjoin(f), e)
108 109
109 110 def delfile(self, f):
110 111 try:
111 112 os.unlink(self.repo.wjoin(f))
112 113 #self.repo.remove([f])
113 114 except:
114 115 pass
115 116
116 117 def putcommit(self, files, parents, author, dest, text):
117 118 seen = {}
118 119 pl = []
119 120 for p in parents:
120 121 if p not in seen:
121 122 pl.append(p)
122 123 seen[p] = 1
123 124 parents = pl
124 125
125 126 if len(parents) < 2: parents.append("0" * 40)
126 127 if len(parents) < 2: parents.append("0" * 40)
127 128 p2 = parents.pop(0)
128 129
129 130 while parents:
130 131 p1 = p2
131 132 p2 = parents.pop(0)
132 133 self.repo.rawcommit(files, text, author, dest,
133 134 hg.bin(p1), hg.bin(p2))
134 135 text = "(octopus merge fixup)\n"
135 136 p2 = hg.hex(self.repo.changelog.tip())
136 137
137 138 return p2
138 139
139 140 def puttags(self, tags):
140 141 try:
141 142 old = self.repo.wfile(".hgtags").read()
142 143 oldlines = old.splitlines(1)
143 144 oldlines.sort()
144 145 except:
145 146 oldlines = []
146 147
147 148 k = tags.keys()
148 149 k.sort()
149 150 newlines = []
150 151 for tag in k:
151 152 newlines.append("%s %s\n" % (tags[tag], tag))
152 153
153 154 newlines.sort()
154 155
155 156 if newlines != oldlines:
156 157 #print "updating tags"
157 158 f = self.repo.wfile(".hgtags", "w")
158 159 f.write("".join(newlines))
159 160 f.close()
160 161 if not oldlines: self.repo.add([".hgtags"])
161 162 date = "%s 0" % int(time.mktime(time.gmtime()))
162 163 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
163 164 date, self.repo.changelog.tip(), hg.nullid)
164 165 return hg.hex(self.repo.changelog.tip())
165 166
166 167 class convert:
167 168 def __init__(self, source, dest, mapfile):
168 169 self.source = source
169 170 self.dest = dest
170 171 self.mapfile = mapfile
171 172 self.commitcache = {}
172 173
173 174 self.map = {}
174 175 try:
175 176 for l in file(self.mapfile):
176 177 sv, dv = l[:-1].split()
177 178 self.map[sv] = dv
178 179 except IOError:
179 180 pass
180 181
181 182 def walktree(self, heads):
182 183 visit = heads
183 184 known = {}
184 185 parents = {}
185 186 while visit:
186 187 n = visit.pop(0)
187 188 if n in known or n in self.map: continue
188 189 known[n] = 1
189 190 self.commitcache[n] = self.source.getcommit(n)
190 191 cp = self.commitcache[n][0]
191 192 for p in cp:
192 193 parents.setdefault(n, []).append(p)
193 194 visit.append(p)
194 195
195 196 return parents
196 197
197 198 def toposort(self, parents):
198 199 visit = parents.keys()
199 200 seen = {}
200 201 children = {}
201 202
202 203 while visit:
203 204 n = visit.pop(0)
204 205 if n in seen: continue
205 206 seen[n] = 1
206 207 pc = 0
207 208 if n in parents:
208 209 for p in parents[n]:
209 210 if p not in self.map: pc += 1
210 211 visit.append(p)
211 212 children.setdefault(p, []).append(n)
212 213 if not pc: root = n
213 214
214 215 s = []
215 216 removed = {}
216 217 visit = children.keys()
217 218 while visit:
218 219 n = visit.pop(0)
219 220 if n in removed: continue
220 221 dep = 0
221 222 if n in parents:
222 223 for p in parents[n]:
223 224 if p in self.map: continue
224 225 if p not in removed:
225 226 # we're still dependent
226 227 visit.append(n)
227 228 dep = 1
228 229 break
229 230
230 231 if not dep:
231 232 # all n's parents are in the list
232 233 removed[n] = 1
233 234 s.append(n)
234 235 if n in children:
235 236 for c in children[n]:
236 237 visit.insert(0, c)
237 238
238 239 return s
239 240
240 241 def copy(self, rev):
241 242 p, a, d, t = self.commitcache[rev]
242 243 files = self.source.getchanges(rev)
243 244
244 245 for f,v,e in files:
245 246 try:
246 247 data = self.source.getfile(f, v)
247 248 except IOError, inst:
248 249 self.dest.delfile(f)
249 250 else:
250 251 self.dest.putfile(f, e, data)
251 252
252 253 r = [self.map[v] for v in p]
253 254 f = [f for f,v,e in files]
254 255 self.map[rev] = self.dest.putcommit(f, r, a, d, t)
255 256 file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))
256 257
257 258 def convert(self):
258 259 heads = self.source.getheads()
259 260 parents = self.walktree(heads)
260 261 t = self.toposort(parents)
261 262 t = [n for n in t if n not in self.map]
262 263 num = len(t)
263 264 c = None
264 265
265 266 for c in t:
266 267 num -= 1
267 268 desc = self.commitcache[c][3].splitlines()[0]
268 269 #print num, desc
269 270 self.copy(c)
270 271
271 272 tags = self.source.gettags()
272 273 ctags = {}
273 274 for k in tags:
274 275 v = tags[k]
275 276 if v in self.map:
276 277 ctags[k] = self.map[v]
277 278
278 279 if c and ctags:
279 280 nrev = self.dest.puttags(ctags)
280 281 # write another hash correspondence to override the previous
281 282 # one so we don't end up with extra tag heads
282 283 file(self.mapfile, "a").write("%s %s\n" % (c, nrev))
283 284
284 285 gitpath, hgpath, mapfile = sys.argv[1:]
285 286 if os.path.isdir(gitpath + "/.git"):
286 287 gitpath += "/.git"
287 288
288 289 c = convert(convert_git(gitpath), convert_mercurial(hgpath), mapfile)
289 290 c.convert()
@@ -1,178 +1,179
1 1 #!/usr/bin/env python
2 2 # Encoding: iso-8859-1
3 3 # vim: tw=80 ts=4 sw=4 noet
4 4 # -----------------------------------------------------------------------------
5 5 # Project : Basic Darcs to Mercurial conversion script
6 6 # -----------------------------------------------------------------------------
7 7 # Authors : Sebastien Pierre <sebastien@xprima.com>
8 8 # TK Soh <teekaysoh@gmail.com>
9 9 # -----------------------------------------------------------------------------
10 10 # Creation : 24-May-2006
11 11 # Last mod : 05-Jun-2006
12 12 # -----------------------------------------------------------------------------
13 13
14 14 import os, sys
15 15 import tempfile
16 16 import xml.dom.minidom as xml_dom
17 17 from time import strptime, mktime
18 18
19 19 DARCS_REPO = None
20 20 HG_REPO = None
21 21
22 22 USAGE = """\
23 23 %s DARCSREPO HGREPO [SKIP]
24 24
25 25 Converts the given Darcs repository to a new Mercurial repository. The given
26 26 HGREPO must not exist, as it will be created and filled up (this will avoid
27 27 overwriting valuable data.
28 28
29 29 In case an error occurs within the process, you can resume the process by
30 30 giving the last successfuly applied change number.
31 31 """ % (os.path.basename(sys.argv[0]))
32 32
33 33 # ------------------------------------------------------------------------------
34 34 #
35 35 # Utilities
36 36 #
37 37 # ------------------------------------------------------------------------------
38 38
39 39 def cmd(text, path=None, silent=False):
40 40 """Executes a command, in the given directory (if any), and returns the
41 41 command result as a string."""
42 42 cwd = None
43 43 if path:
44 44 path = os.path.abspath(path)
45 45 cwd = os.getcwd()
46 46 os.chdir(path)
47 47 if not silent: print "> ", text
48 48 res = os.popen(text).read()
49 49 if path:
50 50 os.chdir(cwd)
51 51 return res
52 52
53 53 def writefile(path, data):
54 54 """Writes the given data into the given file."""
55 55 f = file(path, "w") ; f.write(data) ; f.close()
56 56
57 57 def error( *args ):
58 58 sys.stderr.write("ERROR: ")
59 59 for a in args: sys.stderr.write(str(a))
60 60 sys.stderr.write("\n")
61 61 sys.stderr.write("You can make manual fixes if necessary and then resume by"
62 62 " giving the last changeset number")
63 63 sys.exit(-1)
64 64
65 65 # ------------------------------------------------------------------------------
66 66 #
67 67 # Darcs interface
68 68 #
69 69 # ------------------------------------------------------------------------------
70 70
71 71 def darcs_changes(darcsRepo):
72 72 """Gets the changes list from the given darcs repository. This returns the
73 73 chronological list of changes as (change name, change summary)."""
74 74 changes = cmd("darcs changes --reverse --xml-output", darcsRepo)
75 75 doc = xml_dom.parseString(changes)
76 76 for patch_node in doc.childNodes[0].childNodes:
77 77 name = filter(lambda n:n.nodeName == "name", patch_node.childNodes)
78 78 comm = filter(lambda n:n.nodeName == "comment", patch_node.childNodes)
79 79 if not name:continue
80 80 else: name = name[0].childNodes[0].data
81 81 if not comm: comm = ""
82 82 else: comm = comm[0].childNodes[0].data
83 83 author = patch_node.getAttribute("author")
84 84 date = patch_node.getAttribute("date")
85 85 chash = os.path.splitext(patch_node.getAttribute("hash"))[0]
86 86 yield author, date, name, chash, comm
87 87
88 88 def darcs_tip(darcs_repo):
89 89 changes = cmd("darcs changes",darcs_repo,silent=True)
90 90 changes = filter(lambda l:l.strip().startswith("* "), changes.split("\n"))
91 91 return len(changes)
92 92
93 93 def darcs_pull(hg_repo, darcs_repo, chash):
94 94 old_tip = darcs_tip(darcs_repo)
95 res = cmd("darcs pull '%s' --all --match='hash %s'" % (darcs_repo, chash), hg_repo)
95 res = cmd("darcs pull \"%s\" --all --match=\"hash %s\"" % (darcs_repo, chash), hg_repo)
96 96 print res
97 97 new_tip = darcs_tip(darcs_repo)
98 98 if not new_tip != old_tip + 1:
99 99 error("Darcs pull did not work as expected: " + res)
100 100
101 101 # ------------------------------------------------------------------------------
102 102 #
103 103 # Mercurial interface
104 104 #
105 105 # ------------------------------------------------------------------------------
106 106
107 107 def hg_commit( hg_repo, text, author, date ):
108 108 fd, tmpfile = tempfile.mkstemp(prefix="darcs2hg_")
109 109 writefile(tmpfile, text)
110 110 old_tip = hg_tip(hg_repo)
111 111 cmd("hg add -X _darcs", hg_repo)
112 112 cmd("hg remove -X _darcs --after", hg_repo)
113 res = cmd("hg commit -l %s -u '%s' -d '%s 0'" % (tmpfile, author, date), hg_repo)
113 res = cmd("hg commit -l %s -u \"%s\" -d \"%s 0\"" % (tmpfile, author, date), hg_repo)
114 os.close(fd)
114 115 os.unlink(tmpfile)
115 116 new_tip = hg_tip(hg_repo)
116 117 if not new_tip == old_tip + 1:
117 118 # Sometimes we may have empty commits, we simply skip them
118 119 if res.strip().lower().find("nothing changed") != -1:
119 120 pass
120 121 else:
121 122 error("Mercurial commit did not work as expected: " + res)
122 123
123 124 def hg_tip( hg_repo ):
124 125 """Returns the latest local revision number in the given repository."""
125 126 tip = cmd("hg tip", hg_repo, silent=True)
126 127 tip = tip.split("\n")[0].split(":")[1].strip()
127 128 return int(tip)
128 129
129 130 # ------------------------------------------------------------------------------
130 131 #
131 132 # Main
132 133 #
133 134 # ------------------------------------------------------------------------------
134 135
135 136 if __name__ == "__main__":
136 137 args = sys.argv[1:]
137 138 # We parse the arguments
138 139 if len(args) == 2:
139 140 darcs_repo = os.path.abspath(args[0])
140 141 hg_repo = os.path.abspath(args[1])
141 142 skip = None
142 143 elif len(args) == 3:
143 144 darcs_repo = os.path.abspath(args[0])
144 145 hg_repo = os.path.abspath(args[1])
145 146 skip = int(args[2])
146 147 else:
147 148 print USAGE
148 149 sys.exit(-1)
149 150 # Initializes the target repo
150 151 if not os.path.isdir(darcs_repo + "/_darcs"):
151 152 print "No darcs directory found at: " + darcs_repo
152 153 sys.exit(-1)
153 154 if not os.path.isdir(hg_repo):
154 155 os.mkdir(hg_repo)
155 156 elif skip == None:
156 157 print "Given HG repository must not exist when no SKIP is specified."
157 158 sys.exit(-1)
158 159 if skip == None:
159 cmd("hg init '%s'" % (hg_repo))
160 cmd("hg init \"%s\"" % (hg_repo))
160 161 cmd("darcs initialize", hg_repo)
161 162 # Get the changes from the Darcs repository
162 163 change_number = 0
163 164 for author, date, summary, chash, description in darcs_changes(darcs_repo):
164 165 print "== changeset", change_number,
165 166 if skip != None and change_number <= skip:
166 167 print "(skipping)"
167 168 else:
168 169 text = summary + "\n" + description
169 170 darcs_pull(hg_repo, darcs_repo, chash)
170 171 # The commit hash has a date like 20021020201112
171 172 # --------------------------------YYYYMMDDHHMMSS
172 173 date = chash.split("-")[0]
173 174 epoch = int(mktime(strptime(date, '%Y%m%d%H%M%S')))
174 175 hg_commit(hg_repo, text, author, epoch)
175 176 change_number += 1
176 177 print "Darcs repository (_darcs) was not deleted. You can keep or remove it."
177 178
178 179 # EOF
@@ -1,17 +1,77
1 1 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2 2 <html>
3 3 <head>
4 4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5 5 <meta http-equiv="Content-Style-Type" content="text/css">
6 6 <title></title>
7 7 <style type="text/css">
8 p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica}
8 p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Helvetica}
9 9 p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px}
10 10 </style>
11 11 </head>
12 12 <body>
13 13 <p class="p1">This is a prepackaged release of <a href="http://www.selenic.com/mercurial">Mercurial</a> for Mac OS X.</p>
14 14 <p class="p2"><br></p>
15 <p class="p1">It is based on Mercurial 0.9.</p>
15 <p class="p1">It is based on Mercurial 0.9.1</p>
16 <br>
17 <pre>
18 Release Notes
19 -------------
20
21 2006-07-24 v0.9.1
22
23 Major changes between Mercurial 0.9 and 0.9.1:
24
25 New features:
26 - You can now configure your 'hgweb' server to let remote users
27 'push' changes over http.
28 - You can now 'import' a patch in a mail message by saving the mail
29 message, and importing it. This works for patches sent either
30 inline or as attachments.
31 - The 'diff' command now accepts '-rA:B' syntax as a synonym for
32 '-r A -r B', and adds '-b' and '-B' options.
33
34 New contributions and extensions:
35 - The 'acl' extension lets you lock down parts of a repository
36 against incoming changes
37 - The 'extdiff' extension lets you run your favourite graphical
38 change viewer
39 - Comprehensive integration with the 'vim' editor
40 - A restricted shell for 'ssh'-hosted repositories
41 - An importer for 'darcs' repositories
42
43 New hooks added:
44 - 'preupdate' is run before an update or merge in the working
45 directory.
46 - 'update' is run after an update or merge in the working
47 directory.
48
49 Behaviour changes:
50 - NOTE: Mercurial as installed by the Windows binary
51 installer no longer performs automatic line-ending conversion for
52 Unix/Linux compatibility. To re-enable this feature, edit your
53 'mercurial.ini' file after you upgrade.
54 - The Windows binary installer now automatically adds 'hg' to your
55 '%PATH%'.
56 - The 'backout' command now runs an editor by default, to let you
57 modify the commit message for a backed-out changeset.
58 - An earlier problem with parsing of tags has been fixed.
59 This makes tag parsing slower but more reliable.
60
61 Memory usage and performance improvements:
62 - The 'remove' command has been rewritten to be hundreds of times
63 faster in large repositories.
64 - It is now possible to 'clone' a repository very quickly over a
65 LAN, if the server is configured to allow it. See the new 'server'
66 section in the 'hgrc' documentation.
67
68 Other changes of note:
69 - Mercurial will now print help for an extension if you type 'hg
70 help EXT_NAME'.
71 - The usual array of bug fixes and documentation improvements.
72 - The integrated web server is now more WSGI-compliant.
73 - Work has begun to solidify Mercurial's API for use by third-party
74 packages.
75 </pre>
16 76 </body>
17 77 </html>
@@ -1,1179 +1,1186
1 1 ;;; mercurial.el --- Emacs support for the Mercurial distributed SCM
2 2
3 3 ;; Copyright (C) 2005 Bryan O'Sullivan
4 4
5 5 ;; Author: Bryan O'Sullivan <bos@serpentine.com>
6 6
7 7 ;; mercurial.el is free software; you can redistribute it and/or
8 8 ;; modify it under the terms of version 2 of the GNU General Public
9 9 ;; License as published by the Free Software Foundation.
10 10
11 11 ;; mercurial.el is distributed in the hope that it will be useful, but
12 12 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 14 ;; General Public License for more details.
15 15
16 16 ;; You should have received a copy of the GNU General Public License
17 17 ;; along with mercurial.el, GNU Emacs, or XEmacs; see the file COPYING
18 18 ;; (`C-h C-l'). If not, write to the Free Software Foundation, Inc.,
19 19 ;; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 20
21 21 ;;; Commentary:
22 22
23 23 ;; mercurial.el builds upon Emacs's VC mode to provide flexible
24 24 ;; integration with the Mercurial distributed SCM tool.
25 25
26 26 ;; To get going as quickly as possible, load mercurial.el into Emacs and
27 27 ;; type `C-c h h'; this runs hg-help-overview, which prints a helpful
28 28 ;; usage overview.
29 29
30 30 ;; Much of the inspiration for mercurial.el comes from Rajesh
31 31 ;; Vaidheeswarran's excellent p4.el, which does an admirably thorough
32 32 ;; job for the commercial Perforce SCM product. In fact, substantial
33 33 ;; chunks of code are adapted from p4.el.
34 34
35 35 ;; This code has been developed under XEmacs 21.5, and may not work as
36 36 ;; well under GNU Emacs (albeit tested under 21.4). Patches to
37 37 ;; enhance the portability of this code, fix bugs, and add features
38 38 ;; are most welcome. You can clone a Mercurial repository for this
39 39 ;; package from http://www.serpentine.com/hg/hg-emacs
40 40
41 41 ;; Please send problem reports and suggestions to bos@serpentine.com.
42 42
43 43
44 44 ;;; Code:
45 45
46 46 (require 'advice)
47 47 (require 'cl)
48 48 (require 'diff-mode)
49 49 (require 'easymenu)
50 50 (require 'executable)
51 51 (require 'vc)
52 52
53 53
54 54 ;;; XEmacs has view-less, while GNU Emacs has view. Joy.
55 55
56 56 (condition-case nil
57 57 (require 'view-less)
58 58 (error nil))
59 59 (condition-case nil
60 60 (require 'view)
61 61 (error nil))
62 62
63 63
64 64 ;;; Variables accessible through the custom system.
65 65
66 66 (defgroup mercurial nil
67 67 "Mercurial distributed SCM."
68 68 :group 'tools)
69 69
70 70 (defcustom hg-binary
71 71 (or (executable-find "hg")
72 72 (dolist (path '("~/bin/hg" "/usr/bin/hg" "/usr/local/bin/hg"))
73 73 (when (file-executable-p path)
74 74 (return path))))
75 75 "The path to Mercurial's hg executable."
76 76 :type '(file :must-match t)
77 77 :group 'mercurial)
78 78
79 79 (defcustom hg-mode-hook nil
80 80 "Hook run when a buffer enters hg-mode."
81 81 :type 'sexp
82 82 :group 'mercurial)
83 83
84 84 (defcustom hg-commit-mode-hook nil
85 85 "Hook run when a buffer is created to prepare a commit."
86 86 :type 'sexp
87 87 :group 'mercurial)
88 88
89 89 (defcustom hg-pre-commit-hook nil
90 90 "Hook run before a commit is performed.
91 91 If you want to prevent the commit from proceeding, raise an error."
92 92 :type 'sexp
93 93 :group 'mercurial)
94 94
95 95 (defcustom hg-log-mode-hook nil
96 96 "Hook run after a buffer is filled with log information."
97 97 :type 'sexp
98 98 :group 'mercurial)
99 99
100 100 (defcustom hg-global-prefix "\C-ch"
101 101 "The global prefix for Mercurial keymap bindings."
102 102 :type 'sexp
103 103 :group 'mercurial)
104 104
105 105 (defcustom hg-commit-allow-empty-message nil
106 106 "Whether to allow changes to be committed with empty descriptions."
107 107 :type 'boolean
108 108 :group 'mercurial)
109 109
110 110 (defcustom hg-commit-allow-empty-file-list nil
111 111 "Whether to allow changes to be committed without any modified files."
112 112 :type 'boolean
113 113 :group 'mercurial)
114 114
115 115 (defcustom hg-rev-completion-limit 100
116 116 "The maximum number of revisions that hg-read-rev will offer to complete.
117 117 This affects memory usage and performance when prompting for revisions
118 118 in a repository with a lot of history."
119 119 :type 'integer
120 120 :group 'mercurial)
121 121
122 122 (defcustom hg-log-limit 50
123 123 "The maximum number of revisions that hg-log will display."
124 124 :type 'integer
125 125 :group 'mercurial)
126 126
127 127 (defcustom hg-update-modeline t
128 128 "Whether to update the modeline with the status of a file after every save.
129 129 Set this to nil on platforms with poor process management, such as Windows."
130 130 :type 'boolean
131 131 :group 'mercurial)
132 132
133 133 (defcustom hg-incoming-repository "default"
134 134 "The repository from which changes are pulled from by default.
135 135 This should be a symbolic repository name, since it is used for all
136 136 repository-related commands."
137 137 :type 'string
138 138 :group 'mercurial)
139 139
140 140 (defcustom hg-outgoing-repository "default-push"
141 141 "The repository to which changes are pushed to by default.
142 142 This should be a symbolic repository name, since it is used for all
143 143 repository-related commands."
144 144 :type 'string
145 145 :group 'mercurial)
146 146
147 147
148 148 ;;; Other variables.
149 149
150 150 (defconst hg-running-xemacs (string-match "XEmacs" emacs-version)
151 151 "Is mercurial.el running under XEmacs?")
152 152
153 153 (defvar hg-mode nil
154 154 "Is this file managed by Mercurial?")
155 155 (make-variable-buffer-local 'hg-mode)
156 156 (put 'hg-mode 'permanent-local t)
157 157
158 158 (defvar hg-status nil)
159 159 (make-variable-buffer-local 'hg-status)
160 160 (put 'hg-status 'permanent-local t)
161 161
162 162 (defvar hg-prev-buffer nil)
163 163 (make-variable-buffer-local 'hg-prev-buffer)
164 164 (put 'hg-prev-buffer 'permanent-local t)
165 165
166 166 (defvar hg-root nil)
167 167 (make-variable-buffer-local 'hg-root)
168 168 (put 'hg-root 'permanent-local t)
169 169
170 170 (defvar hg-output-buffer-name "*Hg*"
171 171 "The name to use for Mercurial output buffers.")
172 172
173 173 (defvar hg-file-history nil)
174 174 (defvar hg-repo-history nil)
175 175 (defvar hg-rev-history nil)
176 176
177 177
178 178 ;;; Random constants.
179 179
180 180 (defconst hg-commit-message-start
181 181 "--- Enter your commit message. Type `C-c C-c' to commit. ---\n")
182 182
183 183 (defconst hg-commit-message-end
184 184 "--- Files in bold will be committed. Click to toggle selection. ---\n")
185 185
186 186
187 187 ;;; hg-mode keymap.
188 188
189 189 (defvar hg-mode-map (make-sparse-keymap))
190 190 (define-key hg-mode-map "\C-xv" 'hg-prefix-map)
191 191
192 192 (defvar hg-prefix-map
193 193 (let ((map (copy-keymap vc-prefix-map)))
194 194 (if (functionp 'set-keymap-name)
195 195 (set-keymap-name map 'hg-prefix-map)); XEmacs
196 196 map)
197 197 "This keymap overrides some default vc-mode bindings.")
198 198 (fset 'hg-prefix-map hg-prefix-map)
199 199 (define-key hg-prefix-map "=" 'hg-diff)
200 200 (define-key hg-prefix-map "c" 'hg-undo)
201 201 (define-key hg-prefix-map "g" 'hg-annotate)
202 202 (define-key hg-prefix-map "l" 'hg-log)
203 203 (define-key hg-prefix-map "n" 'hg-commit-start)
204 204 ;; (define-key hg-prefix-map "r" 'hg-update)
205 205 (define-key hg-prefix-map "u" 'hg-revert-buffer)
206 206 (define-key hg-prefix-map "~" 'hg-version-other-window)
207 207
208 208 (add-minor-mode 'hg-mode 'hg-mode hg-mode-map)
209 209
210 210
211 211 ;;; Global keymap.
212 212
213 213 (global-set-key "\C-xvi" 'hg-add)
214 214
215 215 (defvar hg-global-map (make-sparse-keymap))
216 216 (fset 'hg-global-map hg-global-map)
217 217 (global-set-key hg-global-prefix 'hg-global-map)
218 218 (define-key hg-global-map "," 'hg-incoming)
219 219 (define-key hg-global-map "." 'hg-outgoing)
220 220 (define-key hg-global-map "<" 'hg-pull)
221 221 (define-key hg-global-map "=" 'hg-diff-repo)
222 222 (define-key hg-global-map ">" 'hg-push)
223 223 (define-key hg-global-map "?" 'hg-help-overview)
224 224 (define-key hg-global-map "A" 'hg-addremove)
225 225 (define-key hg-global-map "U" 'hg-revert)
226 226 (define-key hg-global-map "a" 'hg-add)
227 227 (define-key hg-global-map "c" 'hg-commit-start)
228 228 (define-key hg-global-map "f" 'hg-forget)
229 229 (define-key hg-global-map "h" 'hg-help-overview)
230 230 (define-key hg-global-map "i" 'hg-init)
231 231 (define-key hg-global-map "l" 'hg-log-repo)
232 232 (define-key hg-global-map "r" 'hg-root)
233 233 (define-key hg-global-map "s" 'hg-status)
234 234 (define-key hg-global-map "u" 'hg-update)
235 235
236 236
237 237 ;;; View mode keymap.
238 238
239 239 (defvar hg-view-mode-map
240 240 (let ((map (copy-keymap (if (boundp 'view-minor-mode-map)
241 241 view-minor-mode-map
242 242 view-mode-map))))
243 243 (if (functionp 'set-keymap-name)
244 244 (set-keymap-name map 'hg-view-mode-map)); XEmacs
245 245 map))
246 246 (fset 'hg-view-mode-map hg-view-mode-map)
247 247 (define-key hg-view-mode-map
248 248 (if hg-running-xemacs [button2] [mouse-2])
249 249 'hg-buffer-mouse-clicked)
250 250
251 251
252 252 ;;; Commit mode keymaps.
253 253
254 254 (defvar hg-commit-mode-map (make-sparse-keymap))
255 255 (define-key hg-commit-mode-map "\C-c\C-c" 'hg-commit-finish)
256 256 (define-key hg-commit-mode-map "\C-c\C-k" 'hg-commit-kill)
257 257 (define-key hg-commit-mode-map "\C-xv=" 'hg-diff-repo)
258 258
259 259 (defvar hg-commit-mode-file-map (make-sparse-keymap))
260 260 (define-key hg-commit-mode-file-map
261 261 (if hg-running-xemacs [button2] [mouse-2])
262 262 'hg-commit-mouse-clicked)
263 263 (define-key hg-commit-mode-file-map " " 'hg-commit-toggle-file)
264 264 (define-key hg-commit-mode-file-map "\r" 'hg-commit-toggle-file)
265 265
266 266
267 267 ;;; Convenience functions.
268 268
269 269 (defsubst hg-binary ()
270 270 (if hg-binary
271 271 hg-binary
272 272 (error "No `hg' executable found!")))
273 273
274 274 (defsubst hg-replace-in-string (str regexp newtext &optional literal)
275 275 "Replace all matches in STR for REGEXP with NEWTEXT string.
276 276 Return the new string. Optional LITERAL non-nil means do a literal
277 277 replacement.
278 278
279 279 This function bridges yet another pointless impedance gap between
280 280 XEmacs and GNU Emacs."
281 281 (if (fboundp 'replace-in-string)
282 282 (replace-in-string str regexp newtext literal)
283 283 (replace-regexp-in-string regexp newtext str nil literal)))
284 284
285 285 (defsubst hg-strip (str)
286 286 "Strip leading and trailing blank lines from a string."
287 287 (hg-replace-in-string (hg-replace-in-string str "[\r\n][ \t\r\n]*\\'" "")
288 288 "\\`[ \t\r\n]*[\r\n]" ""))
289 289
290 290 (defsubst hg-chomp (str)
291 291 "Strip trailing newlines from a string."
292 292 (hg-replace-in-string str "[\r\n]+\'" ""))
293 293
294 294 (defun hg-run-command (command &rest args)
295 295 "Run the shell command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT).
296 296 The list ARGS contains a list of arguments to pass to the command."
297 297 (let* (exit-code
298 298 (output
299 299 (with-output-to-string
300 300 (with-current-buffer
301 301 standard-output
302 302 (setq exit-code
303 303 (apply 'call-process command nil t nil args))))))
304 304 (cons exit-code output)))
305 305
306 306 (defun hg-run (command &rest args)
307 307 "Run the Mercurial command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT)."
308 308 (apply 'hg-run-command (hg-binary) command args))
309 309
310 310 (defun hg-run0 (command &rest args)
311 311 "Run the Mercurial command COMMAND, returning its output.
312 312 If the command does not exit with a zero status code, raise an error."
313 313 (let ((res (apply 'hg-run-command (hg-binary) command args)))
314 314 (if (not (eq (car res) 0))
315 315 (error "Mercurial command failed %s - exit code %s"
316 316 (cons command args)
317 317 (car res))
318 318 (cdr res))))
319 319
320 320 (defun hg-sync-buffers (path)
321 321 "Sync buffers visiting PATH with their on-disk copies.
322 322 If PATH is not being visited, but is under the repository root, sync
323 323 all buffers visiting files in the repository."
324 324 (let ((buf (find-buffer-visiting path)))
325 325 (if buf
326 326 (with-current-buffer buf
327 327 (vc-buffer-sync))
328 328 (hg-do-across-repo path
329 329 (vc-buffer-sync)))))
330 330
331 331 (defun hg-buffer-commands (pnt)
332 332 "Use the properties of a character to do something sensible."
333 333 (interactive "d")
334 334 (let ((rev (get-char-property pnt 'rev))
335 335 (file (get-char-property pnt 'file))
336 336 (date (get-char-property pnt 'date))
337 337 (user (get-char-property pnt 'user))
338 338 (host (get-char-property pnt 'host))
339 339 (prev-buf (current-buffer)))
340 340 (cond
341 341 (file
342 342 (find-file-other-window file))
343 343 (rev
344 344 (hg-diff hg-view-file-name rev rev prev-buf))
345 345 ((message "I don't know how to do that yet")))))
346 346
347 347 (defsubst hg-event-point (event)
348 348 "Return the character position of the mouse event EVENT."
349 349 (if hg-running-xemacs
350 350 (event-point event)
351 351 (posn-point (event-start event))))
352 352
353 353 (defsubst hg-event-window (event)
354 354 "Return the window over which mouse event EVENT occurred."
355 355 (if hg-running-xemacs
356 356 (event-window event)
357 357 (posn-window (event-start event))))
358 358
359 359 (defun hg-buffer-mouse-clicked (event)
360 360 "Translate the mouse clicks in a HG log buffer to character events.
361 361 These are then handed off to `hg-buffer-commands'.
362 362
363 363 Handle frickin' frackin' gratuitous event-related incompatibilities."
364 364 (interactive "e")
365 365 (select-window (hg-event-window event))
366 366 (hg-buffer-commands (hg-event-point event)))
367 367
368 368 (unless (fboundp 'view-minor-mode)
369 369 (defun view-minor-mode (prev-buffer exit-func)
370 370 (view-mode)))
371 371
372 372 (defsubst hg-abbrev-file-name (file)
373 373 "Portable wrapper around abbreviate-file-name."
374 374 (if hg-running-xemacs
375 375 (abbreviate-file-name file t)
376 376 (abbreviate-file-name file)))
377 377
378 378 (defun hg-read-file-name (&optional prompt default)
379 379 "Read a file or directory name, or a pattern, to use with a command."
380 380 (save-excursion
381 381 (while hg-prev-buffer
382 382 (set-buffer hg-prev-buffer))
383 (let ((path (or default (buffer-file-name) default-directory)))
383 (let ((path (or default
384 (buffer-file-name)
385 (expand-file-name default-directory))))
384 386 (if (or (not path) current-prefix-arg)
385 387 (expand-file-name
386 388 (eval (list* 'read-file-name
387 389 (format "File, directory or pattern%s: "
388 390 (or prompt ""))
389 391 (and path (file-name-directory path))
390 392 nil nil
391 393 (and path (file-name-nondirectory path))
392 394 (if hg-running-xemacs
393 395 (cons (quote 'hg-file-history) nil)
394 396 nil))))
395 397 path))))
396 398
397 399 (defun hg-read-number (&optional prompt default)
398 400 "Read a integer value."
399 401 (save-excursion
400 402 (if (or (not default) current-prefix-arg)
401 403 (string-to-number
402 404 (eval (list* 'read-string
403 405 (or prompt "")
404 406 (if default (cons (format "%d" default) nil) nil))))
405 407 default)))
406 408
407 409 (defun hg-read-config ()
408 410 "Return an alist of (key . value) pairs of Mercurial config data.
409 411 Each key is of the form (section . name)."
410 412 (let (items)
411 413 (dolist (line (split-string (hg-chomp (hg-run0 "debugconfig")) "\n") items)
412 414 (string-match "^\\([^=]*\\)=\\(.*\\)" line)
413 415 (let* ((left (substring line (match-beginning 1) (match-end 1)))
414 416 (right (substring line (match-beginning 2) (match-end 2)))
415 417 (key (split-string left "\\."))
416 418 (value (hg-replace-in-string right "\\\\n" "\n" t)))
417 419 (setq items (cons (cons (cons (car key) (cadr key)) value) items))))))
418 420
419 421 (defun hg-config-section (section config)
420 422 "Return an alist of (name . value) pairs for SECTION of CONFIG."
421 423 (let (items)
422 424 (dolist (item config items)
423 425 (when (equal (caar item) section)
424 426 (setq items (cons (cons (cdar item) (cdr item)) items))))))
425 427
426 428 (defun hg-string-starts-with (sub str)
427 429 "Indicate whether string STR starts with the substring or character SUB."
428 430 (if (not (stringp sub))
429 431 (and (> (length str) 0) (equal (elt str 0) sub))
430 432 (let ((sub-len (length sub)))
431 433 (and (<= sub-len (length str))
432 434 (string= sub (substring str 0 sub-len))))))
433 435
434 436 (defun hg-complete-repo (string predicate all)
435 437 "Attempt to complete a repository name.
436 438 We complete on either symbolic names from Mercurial's config or real
437 439 directory names from the file system. We do not penalise URLs."
438 440 (or (if all
439 441 (all-completions string hg-repo-completion-table predicate)
440 442 (try-completion string hg-repo-completion-table predicate))
441 443 (let* ((str (expand-file-name string))
442 444 (dir (file-name-directory str))
443 445 (file (file-name-nondirectory str)))
444 446 (if all
445 447 (let (completions)
446 448 (dolist (name (delete "./" (file-name-all-completions file dir))
447 449 completions)
448 450 (let ((path (concat dir name)))
449 451 (when (file-directory-p path)
450 452 (setq completions (cons name completions))))))
451 453 (let ((comp (file-name-completion file dir)))
452 454 (if comp
453 455 (hg-abbrev-file-name (concat dir comp))))))))
454 456
455 457 (defun hg-read-repo-name (&optional prompt initial-contents default)
456 458 "Read the location of a repository."
457 459 (save-excursion
458 460 (while hg-prev-buffer
459 461 (set-buffer hg-prev-buffer))
460 462 (let (hg-repo-completion-table)
461 463 (if current-prefix-arg
462 464 (progn
463 465 (dolist (path (hg-config-section "paths" (hg-read-config)))
464 466 (setq hg-repo-completion-table
465 467 (cons (cons (car path) t) hg-repo-completion-table))
466 468 (unless (hg-string-starts-with directory-sep-char (cdr path))
467 469 (setq hg-repo-completion-table
468 470 (cons (cons (cdr path) t) hg-repo-completion-table))))
469 471 (completing-read (format "Repository%s: " (or prompt ""))
470 472 'hg-complete-repo
471 473 nil
472 474 nil
473 475 initial-contents
474 476 'hg-repo-history
475 477 default))
476 478 default))))
477 479
478 480 (defun hg-read-rev (&optional prompt default)
479 481 "Read a revision or tag, offering completions."
480 482 (save-excursion
481 483 (while hg-prev-buffer
482 484 (set-buffer hg-prev-buffer))
483 485 (let ((rev (or default "tip")))
484 486 (if current-prefix-arg
485 487 (let ((revs (split-string
486 488 (hg-chomp
487 489 (hg-run0 "-q" "log" "-r"
488 490 (format "-%d:tip" hg-rev-completion-limit)))
489 491 "[\n:]")))
490 492 (dolist (line (split-string (hg-chomp (hg-run0 "tags")) "\n"))
491 493 (setq revs (cons (car (split-string line "\\s-")) revs)))
492 494 (completing-read (format "Revision%s (%s): "
493 495 (or prompt "")
494 496 (or default "tip"))
495 497 (map 'list 'cons revs revs)
496 498 nil
497 499 nil
498 500 nil
499 501 'hg-rev-history
500 502 (or default "tip")))
501 503 rev))))
502 504
503 505 (defmacro hg-do-across-repo (path &rest body)
504 506 (let ((root-name (gensym "root-"))
505 507 (buf-name (gensym "buf-")))
506 508 `(let ((,root-name (hg-root ,path)))
507 509 (save-excursion
508 510 (dolist (,buf-name (buffer-list))
509 511 (set-buffer ,buf-name)
510 512 (when (and hg-status (equal (hg-root buffer-file-name) ,root-name))
511 513 ,@body))))))
512 514
513 515 (put 'hg-do-across-repo 'lisp-indent-function 1)
514 516
515 517
516 518 ;;; View mode bits.
517 519
518 520 (defun hg-exit-view-mode (buf)
519 521 "Exit from hg-view-mode.
520 522 We delete the current window if entering hg-view-mode split the
521 523 current frame."
522 524 (when (and (eq buf (current-buffer))
523 525 (> (length (window-list)) 1))
524 526 (delete-window))
525 527 (when (buffer-live-p buf)
526 528 (kill-buffer buf)))
527 529
528 530 (defun hg-view-mode (prev-buffer &optional file-name)
529 531 (goto-char (point-min))
530 532 (set-buffer-modified-p nil)
531 533 (toggle-read-only t)
532 534 (view-minor-mode prev-buffer 'hg-exit-view-mode)
533 535 (use-local-map hg-view-mode-map)
534 536 (setq truncate-lines t)
535 537 (when file-name
536 538 (set (make-local-variable 'hg-view-file-name)
537 539 (hg-abbrev-file-name file-name))))
538 540
539 541 (defun hg-file-status (file)
540 542 "Return status of FILE, or nil if FILE does not exist or is unmanaged."
541 543 (let* ((s (hg-run "status" file))
542 544 (exit (car s))
543 545 (output (cdr s)))
544 546 (if (= exit 0)
545 547 (let ((state (assoc (substring output 0 (min (length output) 2))
546 548 '(("M " . modified)
547 549 ("A " . added)
548 550 ("R " . removed)
549 551 ("? " . nil)))))
550 552 (if state
551 553 (cdr state)
552 554 'normal)))))
553 555
554 556 (defun hg-tip ()
555 557 (split-string (hg-chomp (hg-run0 "-q" "tip")) ":"))
556 558
557 559 (defmacro hg-view-output (args &rest body)
558 560 "Execute BODY in a clean buffer, then quickly display that buffer.
559 561 If the buffer contains one line, its contents are displayed in the
560 562 minibuffer. Otherwise, the buffer is displayed in view-mode.
561 563 ARGS is of the form (BUFFER-NAME &optional FILE), where BUFFER-NAME is
562 564 the name of the buffer to create, and FILE is the name of the file
563 565 being viewed."
564 566 (let ((prev-buf (gensym "prev-buf-"))
565 567 (v-b-name (car args))
566 568 (v-m-rest (cdr args)))
567 569 `(let ((view-buf-name ,v-b-name)
568 570 (,prev-buf (current-buffer)))
569 571 (get-buffer-create view-buf-name)
570 572 (kill-buffer view-buf-name)
571 573 (get-buffer-create view-buf-name)
572 574 (set-buffer view-buf-name)
573 575 (save-excursion
574 576 ,@body)
575 577 (case (count-lines (point-min) (point-max))
576 578 ((0)
577 579 (kill-buffer view-buf-name)
578 580 (message "(No output)"))
579 581 ((1)
580 582 (let ((msg (hg-chomp (buffer-substring (point-min) (point-max)))))
581 583 (kill-buffer view-buf-name)
582 584 (message "%s" msg)))
583 585 (t
584 586 (pop-to-buffer view-buf-name)
585 587 (setq hg-prev-buffer ,prev-buf)
586 588 (hg-view-mode ,prev-buf ,@v-m-rest))))))
587 589
588 590 (put 'hg-view-output 'lisp-indent-function 1)
589 591
590 592 ;;; Context save and restore across revert.
591 593
592 594 (defun hg-position-context (pos)
593 595 "Return information to help find the given position again."
594 596 (let* ((end (min (point-max) (+ pos 98))))
595 597 (list pos
596 598 (buffer-substring (max (point-min) (- pos 2)) end)
597 599 (- end pos))))
598 600
599 601 (defun hg-buffer-context ()
600 602 "Return information to help restore a user's editing context.
601 603 This is useful across reverts and merges, where a context is likely
602 604 to have moved a little, but not really changed."
603 605 (let ((point-context (hg-position-context (point)))
604 606 (mark-context (let ((mark (mark-marker)))
605 607 (and mark (hg-position-context mark)))))
606 608 (list point-context mark-context)))
607 609
608 610 (defun hg-find-context (ctx)
609 611 "Attempt to find a context in the given buffer.
610 612 Always returns a valid, hopefully sane, position."
611 613 (let ((pos (nth 0 ctx))
612 614 (str (nth 1 ctx))
613 615 (fixup (nth 2 ctx)))
614 616 (save-excursion
615 617 (goto-char (max (point-min) (- pos 15000)))
616 618 (if (and (not (equal str ""))
617 619 (search-forward str nil t))
618 620 (- (point) fixup)
619 621 (max pos (point-min))))))
620 622
621 623 (defun hg-restore-context (ctx)
622 624 "Attempt to restore the user's editing context."
623 625 (let ((point-context (nth 0 ctx))
624 626 (mark-context (nth 1 ctx)))
625 627 (goto-char (hg-find-context point-context))
626 628 (when mark-context
627 629 (set-mark (hg-find-context mark-context)))))
628 630
629 631
630 632 ;;; Hooks.
631 633
632 634 (defun hg-mode-line (&optional force)
633 635 "Update the modeline with the current status of a file.
634 636 An update occurs if optional argument FORCE is non-nil,
635 637 hg-update-modeline is non-nil, or we have not yet checked the state of
636 638 the file."
637 639 (when (and (hg-root) (or force hg-update-modeline (not hg-mode)))
638 640 (let ((status (hg-file-status buffer-file-name)))
639 641 (setq hg-status status
640 642 hg-mode (and status (concat " Hg:"
641 643 (car (hg-tip))
642 644 (cdr (assq status
643 645 '((normal . "")
644 646 (removed . "r")
645 647 (added . "a")
646 648 (modified . "m")))))))
647 649 status)))
648 650
649 651 (defun hg-mode (&optional toggle)
650 652 "Minor mode for Mercurial distributed SCM integration.
651 653
652 654 The Mercurial mode user interface is based on that of VC mode, so if
653 655 you're already familiar with VC, the same keybindings and functions
654 656 will generally work.
655 657
656 658 Below is a list of many common SCM tasks. In the list, `G/L\'
657 659 indicates whether a key binding is global (G) to a repository or local
658 660 (L) to a file. Many commands take a prefix argument.
659 661
660 662 SCM Task G/L Key Binding Command Name
661 663 -------- --- ----------- ------------
662 664 Help overview (what you are reading) G C-c h h hg-help-overview
663 665
664 666 Tell Mercurial to manage a file G C-c h a hg-add
665 667 Commit changes to current file only L C-x v n hg-commit-start
666 668 Undo changes to file since commit L C-x v u hg-revert-buffer
667 669
668 670 Diff file vs last checkin L C-x v = hg-diff
669 671
670 672 View file change history L C-x v l hg-log
671 673 View annotated file L C-x v a hg-annotate
672 674
673 675 Diff repo vs last checkin G C-c h = hg-diff-repo
674 676 View status of files in repo G C-c h s hg-status
675 677 Commit all changes G C-c h c hg-commit-start
676 678
677 679 Undo all changes since last commit G C-c h U hg-revert
678 680 View repo change history G C-c h l hg-log-repo
679 681
680 682 See changes that can be pulled G C-c h , hg-incoming
681 683 Pull changes G C-c h < hg-pull
682 684 Update working directory after pull G C-c h u hg-update
683 685 See changes that can be pushed G C-c h . hg-outgoing
684 686 Push changes G C-c h > hg-push"
685 687 (unless vc-make-backup-files
686 688 (set (make-local-variable 'backup-inhibited) t))
687 689 (run-hooks 'hg-mode-hook))
688 690
689 691 (defun hg-find-file-hook ()
690 692 (when (hg-mode-line)
691 693 (hg-mode)))
692 694
693 695 (add-hook 'find-file-hooks 'hg-find-file-hook)
694 696
695 697 (defun hg-after-save-hook ()
696 698 (let ((old-status hg-status))
697 699 (hg-mode-line)
698 700 (if (and (not old-status) hg-status)
699 701 (hg-mode))))
700 702
701 703 (add-hook 'after-save-hook 'hg-after-save-hook)
702 704
703 705
704 706 ;;; User interface functions.
705 707
706 708 (defun hg-help-overview ()
707 709 "This is an overview of the Mercurial SCM mode for Emacs.
708 710
709 711 You can find the source code, license (GPL v2), and credits for this
710 712 code by typing `M-x find-library mercurial RET'."
711 713 (interactive)
712 714 (hg-view-output ("Mercurial Help Overview")
713 715 (insert (documentation 'hg-help-overview))
714 716 (let ((pos (point)))
715 717 (insert (documentation 'hg-mode))
716 718 (goto-char pos)
717 719 (end-of-line 1)
718 720 (delete-region pos (point)))
719 (cd (hg-root))))
721 (let ((hg-root-dir (hg-root)))
722 (if (not hg-root-dir)
723 (error "error: %s: directory is not part of a Mercurial repository."
724 default-directory)
725 (cd hg-root-dir)))))
720 726
721 727 (defun hg-add (path)
722 728 "Add PATH to the Mercurial repository on the next commit.
723 729 With a prefix argument, prompt for the path to add."
724 730 (interactive (list (hg-read-file-name " to add")))
725 731 (let ((buf (current-buffer))
726 732 (update (equal buffer-file-name path)))
727 733 (hg-view-output (hg-output-buffer-name)
728 734 (apply 'call-process (hg-binary) nil t nil (list "add" path))
729 735 ;; "hg add" shows pathes relative NOT TO ROOT BUT TO REPOSITORY
730 736 (replace-regexp " \\.\\.." " " nil 0 (buffer-size))
731 737 (goto-char 0)
732 738 (cd (hg-root path)))
733 739 (when update
734 740 (unless vc-make-backup-files
735 741 (set (make-local-variable 'backup-inhibited) t))
736 742 (with-current-buffer buf
737 743 (hg-mode-line)))))
738 744
739 745 (defun hg-addremove ()
740 746 (interactive)
741 747 (error "not implemented"))
742 748
743 749 (defun hg-annotate ()
744 750 (interactive)
745 751 (error "not implemented"))
746 752
747 753 (defun hg-commit-toggle-file (pos)
748 754 "Toggle whether or not the file at POS will be committed."
749 755 (interactive "d")
750 756 (save-excursion
751 757 (goto-char pos)
752 758 (let ((face (get-text-property pos 'face))
753 759 (inhibit-read-only t)
754 760 bol)
755 761 (beginning-of-line)
756 762 (setq bol (+ (point) 4))
757 763 (end-of-line)
758 764 (if (eq face 'bold)
759 765 (progn
760 766 (remove-text-properties bol (point) '(face nil))
761 767 (message "%s will not be committed"
762 768 (buffer-substring bol (point))))
763 769 (add-text-properties bol (point) '(face bold))
764 770 (message "%s will be committed"
765 771 (buffer-substring bol (point)))))))
766 772
767 773 (defun hg-commit-mouse-clicked (event)
768 774 "Toggle whether or not the file at POS will be committed."
769 775 (interactive "@e")
770 776 (hg-commit-toggle-file (hg-event-point event)))
771 777
772 778 (defun hg-commit-kill ()
773 779 "Kill the commit currently being prepared."
774 780 (interactive)
775 781 (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this commit? "))
776 782 (let ((buf hg-prev-buffer))
777 783 (kill-buffer nil)
778 784 (switch-to-buffer buf))))
779 785
780 786 (defun hg-commit-finish ()
781 787 "Finish preparing a commit, and perform the actual commit.
782 788 The hook hg-pre-commit-hook is run before anything else is done. If
783 789 the commit message is empty and hg-commit-allow-empty-message is nil,
784 790 an error is raised. If the list of files to commit is empty and
785 791 hg-commit-allow-empty-file-list is nil, an error is raised."
786 792 (interactive)
787 793 (let ((root hg-root))
788 794 (save-excursion
789 795 (run-hooks 'hg-pre-commit-hook)
790 796 (goto-char (point-min))
791 797 (search-forward hg-commit-message-start)
792 798 (let (message files)
793 799 (let ((start (point)))
794 800 (goto-char (point-max))
795 801 (search-backward hg-commit-message-end)
796 802 (setq message (hg-strip (buffer-substring start (point)))))
797 803 (when (and (= (length message) 0)
798 804 (not hg-commit-allow-empty-message))
799 805 (error "Cannot proceed - commit message is empty"))
800 806 (forward-line 1)
801 807 (beginning-of-line)
802 808 (while (< (point) (point-max))
803 809 (let ((pos (+ (point) 4)))
804 810 (end-of-line)
805 811 (when (eq (get-text-property pos 'face) 'bold)
806 812 (end-of-line)
807 813 (setq files (cons (buffer-substring pos (point)) files))))
808 814 (forward-line 1))
809 815 (when (and (= (length files) 0)
810 816 (not hg-commit-allow-empty-file-list))
811 817 (error "Cannot proceed - no files to commit"))
812 818 (setq message (concat message "\n"))
813 819 (apply 'hg-run0 "--cwd" hg-root "commit" "-m" message files))
814 820 (let ((buf hg-prev-buffer))
815 821 (kill-buffer nil)
816 822 (switch-to-buffer buf))
817 823 (hg-do-across-repo root
818 824 (hg-mode-line)))))
819 825
820 826 (defun hg-commit-mode ()
821 827 "Mode for describing a commit of changes to a Mercurial repository.
822 828 This involves two actions: describing the changes with a commit
823 829 message, and choosing the files to commit.
824 830
825 831 To describe the commit, simply type some text in the designated area.
826 832
827 833 By default, all modified, added and removed files are selected for
828 834 committing. Files that will be committed are displayed in bold face\;
829 835 those that will not are displayed in normal face.
830 836
831 837 To toggle whether a file will be committed, move the cursor over a
832 838 particular file and hit space or return. Alternatively, middle click
833 839 on the file.
834 840
835 841 Key bindings
836 842 ------------
837 843 \\[hg-commit-finish] proceed with commit
838 844 \\[hg-commit-kill] kill commit
839 845
840 846 \\[hg-diff-repo] view diff of pending changes"
841 847 (interactive)
842 848 (use-local-map hg-commit-mode-map)
843 849 (set-syntax-table text-mode-syntax-table)
844 850 (setq local-abbrev-table text-mode-abbrev-table
845 851 major-mode 'hg-commit-mode
846 852 mode-name "Hg-Commit")
847 853 (set-buffer-modified-p nil)
848 854 (setq buffer-undo-list nil)
849 855 (run-hooks 'text-mode-hook 'hg-commit-mode-hook))
850 856
851 857 (defun hg-commit-start ()
852 858 "Prepare a commit of changes to the repository containing the current file."
853 859 (interactive)
854 860 (while hg-prev-buffer
855 861 (set-buffer hg-prev-buffer))
856 862 (let ((root (hg-root))
857 863 (prev-buffer (current-buffer))
858 864 modified-files)
859 865 (unless root
860 866 (error "Cannot commit outside a repository!"))
861 867 (hg-sync-buffers root)
862 868 (setq modified-files (hg-chomp (hg-run0 "--cwd" root "status" "-arm")))
863 869 (when (and (= (length modified-files) 0)
864 870 (not hg-commit-allow-empty-file-list))
865 871 (error "No pending changes to commit"))
866 872 (let* ((buf-name (format "*Mercurial: Commit %s*" root)))
867 873 (pop-to-buffer (get-buffer-create buf-name))
868 874 (when (= (point-min) (point-max))
869 875 (set (make-local-variable 'hg-root) root)
870 876 (setq hg-prev-buffer prev-buffer)
871 877 (insert "\n")
872 878 (let ((bol (point)))
873 879 (insert hg-commit-message-end)
874 880 (add-text-properties bol (point) '(face bold-italic)))
875 881 (let ((file-area (point)))
876 882 (insert modified-files)
877 883 (goto-char file-area)
878 884 (while (< (point) (point-max))
879 885 (let ((bol (point)))
880 886 (forward-char 1)
881 887 (insert " ")
882 888 (end-of-line)
883 889 (add-text-properties (+ bol 4) (point)
884 890 '(face bold mouse-face highlight)))
885 891 (forward-line 1))
886 892 (goto-char file-area)
887 893 (add-text-properties (point) (point-max)
888 894 `(keymap ,hg-commit-mode-file-map))
889 895 (goto-char (point-min))
890 896 (insert hg-commit-message-start)
891 897 (add-text-properties (point-min) (point) '(face bold-italic))
892 898 (insert "\n\n")
893 899 (forward-line -1)
894 900 (save-excursion
895 901 (goto-char (point-max))
896 902 (search-backward hg-commit-message-end)
897 903 (add-text-properties (match-beginning 0) (point-max)
898 904 '(read-only t))
899 905 (goto-char (point-min))
900 906 (search-forward hg-commit-message-start)
901 907 (add-text-properties (match-beginning 0) (match-end 0)
902 908 '(read-only t)))
903 909 (hg-commit-mode)
904 910 (cd root))))))
905 911
906 912 (defun hg-diff (path &optional rev1 rev2)
907 913 "Show the differences between REV1 and REV2 of PATH.
908 914 When called interactively, the default behaviour is to treat REV1 as
909 915 the \"parent\" revision, REV2 as the current edited version of the file, and
910 916 PATH as the file edited in the current buffer.
911 917 With a prefix argument, prompt for all of these."
912 918 (interactive (list (hg-read-file-name " to diff")
913 919 (let ((rev1 (hg-read-rev " to start with" 'parent)))
914 920 (and (not (eq rev1 'parent)) rev1))
915 921 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
916 922 (and (not (eq rev2 'working-dir)) rev2))))
917 923 (hg-sync-buffers path)
918 924 (let ((a-path (hg-abbrev-file-name path))
919 925 ;; none revision is specified explicitly
920 926 (none (and (not rev1) (not rev2)))
921 927 ;; only one revision is specified explicitly
922 928 (one (or (and (or (equal rev1 rev2) (not rev2)) rev1)
923 929 (and (not rev1) rev2)))
924 930 diff)
925 931 (hg-view-output ((cond
926 932 (none
927 933 (format "Mercurial: Diff against parent of %s" a-path))
928 934 (one
929 935 (format "Mercurial: Diff of rev %s of %s" one a-path))
930 936 (t
931 937 (format "Mercurial: Diff from rev %s to %s of %s"
932 938 rev1 rev2 a-path))))
933 939 (cond
934 940 (none
935 941 (call-process (hg-binary) nil t nil "diff" path))
936 942 (one
937 943 (call-process (hg-binary) nil t nil "diff" "-r" one path))
938 944 (t
939 945 (call-process (hg-binary) nil t nil "diff" "-r" rev1 "-r" rev2 path)))
940 946 (diff-mode)
941 947 (setq diff (not (= (point-min) (point-max))))
942 948 (font-lock-fontify-buffer)
943 949 (cd (hg-root path)))
944 950 diff))
945 951
946 952 (defun hg-diff-repo (path &optional rev1 rev2)
947 953 "Show the differences between REV1 and REV2 of repository containing PATH.
948 954 When called interactively, the default behaviour is to treat REV1 as
949 955 the \"parent\" revision, REV2 as the current edited version of the file, and
950 956 PATH as the `hg-root' of the current buffer.
951 957 With a prefix argument, prompt for all of these."
952 958 (interactive (list (hg-read-file-name " to diff")
953 959 (let ((rev1 (hg-read-rev " to start with" 'parent)))
954 960 (and (not (eq rev1 'parent)) rev1))
955 961 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
956 962 (and (not (eq rev2 'working-dir)) rev2))))
957 963 (hg-diff (hg-root path) rev1 rev2))
958 964
959 965 (defun hg-forget (path)
960 966 "Lose track of PATH, which has been added, but not yet committed.
961 967 This will prevent the file from being incorporated into the Mercurial
962 968 repository on the next commit.
963 969 With a prefix argument, prompt for the path to forget."
964 970 (interactive (list (hg-read-file-name " to forget")))
965 971 (let ((buf (current-buffer))
966 972 (update (equal buffer-file-name path)))
967 973 (hg-view-output (hg-output-buffer-name)
968 974 (apply 'call-process (hg-binary) nil t nil (list "forget" path))
969 975 ;; "hg forget" shows pathes relative NOT TO ROOT BUT TO REPOSITORY
970 976 (replace-regexp " \\.\\.." " " nil 0 (buffer-size))
971 977 (goto-char 0)
972 978 (cd (hg-root path)))
973 979 (when update
974 980 (with-current-buffer buf
975 (set (make-local-variable 'backup-inhibited) nil)
981 (when (local-variable-p 'backup-inhibited)
982 (kill-local-variable 'backup-inhibited))
976 983 (hg-mode-line)))))
977 984
978 985 (defun hg-incoming (&optional repo)
979 986 "Display changesets present in REPO that are not present locally."
980 987 (interactive (list (hg-read-repo-name " where changes would come from")))
981 988 (hg-view-output ((format "Mercurial: Incoming from %s to %s"
982 989 (hg-abbrev-file-name (hg-root))
983 990 (hg-abbrev-file-name
984 991 (or repo hg-incoming-repository))))
985 992 (call-process (hg-binary) nil t nil "incoming"
986 993 (or repo hg-incoming-repository))
987 994 (hg-log-mode)
988 995 (cd (hg-root))))
989 996
990 997 (defun hg-init ()
991 998 (interactive)
992 999 (error "not implemented"))
993 1000
994 1001 (defun hg-log-mode ()
995 1002 "Mode for viewing a Mercurial change log."
996 1003 (goto-char (point-min))
997 1004 (when (looking-at "^searching for changes.*$")
998 1005 (delete-region (match-beginning 0) (match-end 0)))
999 1006 (run-hooks 'hg-log-mode-hook))
1000 1007
1001 1008 (defun hg-log (path &optional rev1 rev2 log-limit)
1002 1009 "Display the revision history of PATH.
1003 1010 History is displayed between REV1 and REV2.
1004 1011 Number of displayed changesets is limited to LOG-LIMIT.
1005 1012 REV1 defaults to the tip, while
1006 1013 REV2 defaults to `hg-rev-completion-limit' changes from the tip revision.
1007 1014 LOG-LIMIT defaults to `hg-log-limit'.
1008 1015 With a prefix argument, prompt for each parameter."
1009 1016 (interactive (list (hg-read-file-name " to log")
1010 1017 (hg-read-rev " to start with"
1011 1018 "tip")
1012 1019 (hg-read-rev " to end with"
1013 1020 (format "%d" (- hg-rev-completion-limit)))
1014 1021 (hg-read-number "Output limited to: "
1015 1022 hg-log-limit)))
1016 1023 (let ((a-path (hg-abbrev-file-name path))
1017 1024 (r1 (or rev1 (format "-%d" hg-rev-completion-limit)))
1018 1025 (r2 (or rev2 rev1 "tip"))
1019 1026 (limit (format "%d" (or log-limit hg-log-limit))))
1020 1027 (hg-view-output ((if (equal r1 r2)
1021 1028 (format "Mercurial: Log of rev %s of %s" rev1 a-path)
1022 1029 (format
1023 1030 "Mercurial: at most %s log(s) from rev %s to %s of %s"
1024 1031 limit r1 r2 a-path)))
1025 1032 (eval (list* 'call-process (hg-binary) nil t nil
1026 1033 "log"
1027 1034 "-r" (format "%s:%s" r1 r2)
1028 1035 "-l" limit
1029 1036 (if (> (length path) (length (hg-root path)))
1030 1037 (cons path nil)
1031 1038 nil)))
1032 1039 (hg-log-mode)
1033 1040 (cd (hg-root path)))))
1034 1041
1035 1042 (defun hg-log-repo (path &optional rev1 rev2 log-limit)
1036 1043 "Display the revision history of the repository containing PATH.
1037 1044 History is displayed between REV1 and REV2.
1038 1045 Number of displayed changesets is limited to LOG-LIMIT,
1039 1046 REV1 defaults to the tip, while
1040 1047 REV2 defaults to `hg-rev-completion-limit' changes from the tip revision.
1041 1048 LOG-LIMIT defaults to `hg-log-limit'.
1042 1049 With a prefix argument, prompt for each parameter."
1043 1050 (interactive (list (hg-read-file-name " to log")
1044 1051 (hg-read-rev " to start with"
1045 1052 "tip")
1046 1053 (hg-read-rev " to end with"
1047 1054 (format "%d" (- hg-rev-completion-limit)))
1048 1055 (hg-read-number "Output limited to: "
1049 1056 hg-log-limit)))
1050 1057 (hg-log (hg-root path) rev1 rev2 log-limit))
1051 1058
1052 1059 (defun hg-outgoing (&optional repo)
1053 1060 "Display changesets present locally that are not present in REPO."
1054 1061 (interactive (list (hg-read-repo-name " where changes would go to" nil
1055 1062 hg-outgoing-repository)))
1056 1063 (hg-view-output ((format "Mercurial: Outgoing from %s to %s"
1057 1064 (hg-abbrev-file-name (hg-root))
1058 1065 (hg-abbrev-file-name
1059 1066 (or repo hg-outgoing-repository))))
1060 1067 (call-process (hg-binary) nil t nil "outgoing"
1061 1068 (or repo hg-outgoing-repository))
1062 1069 (hg-log-mode)
1063 1070 (cd (hg-root))))
1064 1071
1065 1072 (defun hg-pull (&optional repo)
1066 1073 "Pull changes from repository REPO.
1067 1074 This does not update the working directory."
1068 1075 (interactive (list (hg-read-repo-name " to pull from")))
1069 1076 (hg-view-output ((format "Mercurial: Pull to %s from %s"
1070 1077 (hg-abbrev-file-name (hg-root))
1071 1078 (hg-abbrev-file-name
1072 1079 (or repo hg-incoming-repository))))
1073 1080 (call-process (hg-binary) nil t nil "pull"
1074 1081 (or repo hg-incoming-repository))
1075 1082 (cd (hg-root))))
1076 1083
1077 1084 (defun hg-push (&optional repo)
1078 1085 "Push changes to repository REPO."
1079 1086 (interactive (list (hg-read-repo-name " to push to")))
1080 1087 (hg-view-output ((format "Mercurial: Push from %s to %s"
1081 1088 (hg-abbrev-file-name (hg-root))
1082 1089 (hg-abbrev-file-name
1083 1090 (or repo hg-outgoing-repository))))
1084 1091 (call-process (hg-binary) nil t nil "push"
1085 1092 (or repo hg-outgoing-repository))
1086 1093 (cd (hg-root))))
1087 1094
1088 1095 (defun hg-revert-buffer-internal ()
1089 1096 (let ((ctx (hg-buffer-context)))
1090 1097 (message "Reverting %s..." buffer-file-name)
1091 1098 (hg-run0 "revert" buffer-file-name)
1092 1099 (revert-buffer t t t)
1093 1100 (hg-restore-context ctx)
1094 1101 (hg-mode-line)
1095 1102 (message "Reverting %s...done" buffer-file-name)))
1096 1103
1097 1104 (defun hg-revert-buffer ()
1098 1105 "Revert current buffer's file back to the latest committed version.
1099 1106 If the file has not changed, nothing happens. Otherwise, this
1100 1107 displays a diff and asks for confirmation before reverting."
1101 1108 (interactive)
1102 1109 (let ((vc-suppress-confirm nil)
1103 1110 (obuf (current-buffer))
1104 1111 diff)
1105 1112 (vc-buffer-sync)
1106 1113 (unwind-protect
1107 1114 (setq diff (hg-diff buffer-file-name))
1108 1115 (when diff
1109 1116 (unless (yes-or-no-p "Discard changes? ")
1110 1117 (error "Revert cancelled")))
1111 1118 (when diff
1112 1119 (let ((buf (current-buffer)))
1113 1120 (delete-window (selected-window))
1114 1121 (kill-buffer buf))))
1115 1122 (set-buffer obuf)
1116 1123 (when diff
1117 1124 (hg-revert-buffer-internal))))
1118 1125
1119 1126 (defun hg-root (&optional path)
1120 1127 "Return the root of the repository that contains the given path.
1121 1128 If the path is outside a repository, return nil.
1122 1129 When called interactively, the root is printed. A prefix argument
1123 1130 prompts for a path to check."
1124 1131 (interactive (list (hg-read-file-name)))
1125 1132 (if (or path (not hg-root))
1126 1133 (let ((root (do ((prev nil dir)
1127 1134 (dir (file-name-directory
1128 1135 (or
1129 1136 path
1130 1137 buffer-file-name
1131 1138 (expand-file-name default-directory)))
1132 1139 (file-name-directory (directory-file-name dir))))
1133 1140 ((equal prev dir))
1134 1141 (when (file-directory-p (concat dir ".hg"))
1135 1142 (return dir)))))
1136 1143 (when (interactive-p)
1137 1144 (if root
1138 1145 (message "The root of this repository is `%s'." root)
1139 1146 (message "The path `%s' is not in a Mercurial repository."
1140 1147 (hg-abbrev-file-name path))))
1141 1148 root)
1142 1149 hg-root))
1143 1150
1144 1151 (defun hg-status (path)
1145 1152 "Print revision control status of a file or directory.
1146 1153 With prefix argument, prompt for the path to give status for.
1147 1154 Names are displayed relative to the repository root."
1148 1155 (interactive (list (hg-read-file-name " for status" (hg-root))))
1149 1156 (let ((root (hg-root)))
1150 1157 (hg-view-output ((format "Mercurial: Status of %s in %s"
1151 1158 (let ((name (substring (expand-file-name path)
1152 1159 (length root))))
1153 1160 (if (> (length name) 0)
1154 1161 name
1155 1162 "*"))
1156 1163 (hg-abbrev-file-name root)))
1157 1164 (apply 'call-process (hg-binary) nil t nil
1158 1165 (list "--cwd" root "status" path))
1159 1166 (cd (hg-root path)))))
1160 1167
1161 1168 (defun hg-undo ()
1162 1169 (interactive)
1163 1170 (error "not implemented"))
1164 1171
1165 1172 (defun hg-update ()
1166 1173 (interactive)
1167 1174 (error "not implemented"))
1168 1175
1169 1176 (defun hg-version-other-window ()
1170 1177 (interactive)
1171 1178 (error "not implemented"))
1172 1179
1173 1180
1174 1181 (provide 'mercurial)
1175 1182
1176 1183
1177 1184 ;;; Local Variables:
1178 1185 ;;; prompt-to-byte-compile: nil
1179 1186 ;;; end:
This diff has been collapsed as it changes many lines, (686 lines changed) Show them Hide them
@@ -1,1689 +1,1697
1 1 " vim600: set foldmethod=marker:
2 2 "
3 3 " Vim plugin to assist in working with HG-controlled files.
4 4 "
5 5 " Last Change: 2006/02/22
6 " Version: 1.76
6 " Version: 1.77
7 7 " Maintainer: Mathieu Clabaut <mathieu.clabaut@gmail.com>
8 8 " License: This file is placed in the public domain.
9 9 " Credits:
10 10 " Bob Hiestand <bob.hiestand@gmail.com> for the fabulous
11 11 " cvscommand.vim from which this script was directly created by
12 12 " means of sed commands and minor tweaks.
13 13
14 14 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
15 15 "
16 " Section: Documentation
16 " Section: Documentation
17 17 "----------------------------
18 18 "
19 19 " Documentation should be available by ":help hgcommand" command, once the
20 20 " script has been copied in you .vim/plugin directory.
21 21 "
22 22 " You still can read the documentation at the end of this file. Locate it by
23 23 " searching the "hgcommand-contents" string (and set ft=help to have
24 " appropriate syntaxic coloration).
24 " appropriate syntaxic coloration).
25 25
26 26 " Section: Plugin header {{{1
27 27
28 28 " loaded_hgcommand is set to 1 when the initialization begins, and 2 when it
29 29 " completes. This allows various actions to only be taken by functions after
30 30 " system initialization.
31 31
32 32 if exists("loaded_hgcommand")
33 33 finish
34 34 endif
35 35 let loaded_hgcommand = 1
36 36
37 " store 'compatible' settings
38 let s:save_cpo = &cpo
39 set cpo&vim
40
41 " run checks
42 let s:script_name = expand("<sfile>:t:r")
43
44 function! s:HGCleanupOnFailure(err)
45 echohl WarningMsg
46 echomsg s:script_name . ":" a:err "Plugin not loaded"
47 echohl None
48 let loaded_hgcommand = "no"
49 unlet s:save_cpo s:script_name
50 endfunction
51
37 52 if v:version < 602
38 echohl WarningMsg|echomsg "HGCommand 1.69 or later requires VIM 6.2 or later"|echohl None
53 call <SID>HGCleanupOnFailure("VIM 6.2 or later required.")
39 54 finish
40 55 endif
41 56
57 if !exists("*system")
58 call <SID>HGCleanupOnFailure("builtin system() function required.")
59 finish
60 endif
61
62 let s:script_version = "v0.2"
63
42 64 " Section: Event group setup {{{1
43 65
44 66 augroup HGCommand
45 67 augroup END
46 68
47 69 " Section: Plugin initialization {{{1
48 70 silent do HGCommand User HGPluginInit
49 71
50 72 " Section: Script variable initialization {{{1
51 73
52 74 let s:HGCommandEditFileRunning = 0
53 75 unlet! s:vimDiffRestoreCmd
54 76 unlet! s:vimDiffSourceBuffer
55 77 unlet! s:vimDiffBufferCount
56 78 unlet! s:vimDiffScratchList
57 79
58 80 " Section: Utility functions {{{1
59 81
60 82 " Function: s:HGResolveLink() {{{2
61 83 " Fully resolve the given file name to remove shortcuts or symbolic links.
62 84
63 85 function! s:HGResolveLink(fileName)
64 86 let resolved = resolve(a:fileName)
65 87 if resolved != a:fileName
66 let resolved = s:HGResolveLink(resolved)
88 let resolved = <SID>HGResolveLink(resolved)
67 89 endif
68 90 return resolved
69 91 endfunction
70 92
71 93 " Function: s:HGChangeToCurrentFileDir() {{{2
72 94 " Go to the directory in which the current HG-controlled file is located.
73 95 " If this is a HG command buffer, first switch to the original file.
74 96
75 97 function! s:HGChangeToCurrentFileDir(fileName)
76 98 let oldCwd=getcwd()
77 let fileName=s:HGResolveLink(a:fileName)
99 let fileName=<SID>HGResolveLink(a:fileName)
78 100 let newCwd=fnamemodify(fileName, ':h')
79 101 if strlen(newCwd) > 0
80 102 execute 'cd' escape(newCwd, ' ')
81 103 endif
82 104 return oldCwd
83 105 endfunction
84 106
85 " Function: s:HGGetOption(name, default) {{{2
107 " Function: <SID>HGGetOption(name, default) {{{2
86 108 " Grab a user-specified option to override the default provided. Options are
87 109 " searched in the window, buffer, then global spaces.
88 110
89 111 function! s:HGGetOption(name, default)
90 112 if exists("s:" . a:name . "Override")
91 113 execute "return s:".a:name."Override"
92 114 elseif exists("w:" . a:name)
93 115 execute "return w:".a:name
94 116 elseif exists("b:" . a:name)
95 117 execute "return b:".a:name
96 118 elseif exists("g:" . a:name)
97 119 execute "return g:".a:name
98 120 else
99 121 return a:default
100 122 endif
101 123 endfunction
102 124
103 125 " Function: s:HGEditFile(name, origBuffNR) {{{2
104 126 " Wrapper around the 'edit' command to provide some helpful error text if the
105 127 " current buffer can't be abandoned. If name is provided, it is used;
106 128 " otherwise, a nameless scratch buffer is used.
107 129 " Returns: 0 if successful, -1 if an error occurs.
108 130
109 131 function! s:HGEditFile(name, origBuffNR)
110 132 "Name parameter will be pasted into expression.
111 133 let name = escape(a:name, ' *?\')
112 134
113 let editCommand = s:HGGetOption('HGCommandEdit', 'edit')
135 let editCommand = <SID>HGGetOption('HGCommandEdit', 'edit')
114 136 if editCommand != 'edit'
115 if s:HGGetOption('HGCommandSplit', 'horizontal') == 'horizontal'
137 if <SID>HGGetOption('HGCommandSplit', 'horizontal') == 'horizontal'
116 138 if name == ""
117 139 let editCommand = 'rightbelow new'
118 140 else
119 141 let editCommand = 'rightbelow split ' . name
120 142 endif
121 143 else
122 144 if name == ""
123 145 let editCommand = 'vert rightbelow new'
124 146 else
125 147 let editCommand = 'vert rightbelow split ' . name
126 148 endif
127 149 endif
128 150 else
129 151 if name == ""
130 152 let editCommand = 'enew'
131 153 else
132 154 let editCommand = 'edit ' . name
133 155 endif
134 156 endif
135 157
136 158 " Protect against useless buffer set-up
137 159 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
138 160 try
139 161 execute editCommand
140 162 finally
141 163 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
142 164 endtry
143 165
144 166 let b:HGOrigBuffNR=a:origBuffNR
145 167 let b:HGCommandEdit='split'
146 168 endfunction
147 169
148 170 " Function: s:HGCreateCommandBuffer(cmd, cmdName, statusText, filename) {{{2
149 171 " Creates a new scratch buffer and captures the output from execution of the
150 172 " given command. The name of the scratch buffer is returned.
151 173
152 174 function! s:HGCreateCommandBuffer(cmd, cmdName, statusText, origBuffNR)
153 175 let fileName=bufname(a:origBuffNR)
154 176
155 177 let resultBufferName=''
156 178
157 if s:HGGetOption("HGCommandNameResultBuffers", 0)
158 let nameMarker = s:HGGetOption("HGCommandNameMarker", '_')
179 if <SID>HGGetOption("HGCommandNameResultBuffers", 0)
180 let nameMarker = <SID>HGGetOption("HGCommandNameMarker", '_')
159 181 if strlen(a:statusText) > 0
160 182 let bufName=a:cmdName . ' -- ' . a:statusText
161 183 else
162 184 let bufName=a:cmdName
163 185 endif
164 186 let bufName=fileName . ' ' . nameMarker . bufName . nameMarker
165 187 let counter=0
166 188 let resultBufferName = bufName
167 189 while buflisted(resultBufferName)
168 190 let counter=counter + 1
169 191 let resultBufferName=bufName . ' (' . counter . ')'
170 192 endwhile
171 193 endif
172 194
173 let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " " . a:cmd
195 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " " . a:cmd
174 196 "echomsg "DBG :".hgCommand
175 197 let hgOut = system(hgCommand)
176 198 " HACK: diff command does not return proper error codes
177 199 if v:shell_error && a:cmdName != 'hgdiff'
178 200 if strlen(hgOut) == 0
179 201 echoerr "HG command failed"
180 202 else
181 203 echoerr "HG command failed: " . hgOut
182 204 endif
183 205 return -1
184 206 endif
185 207 if strlen(hgOut) == 0
186 208 " Handle case of no output. In this case, it is important to check the
187 209 " file status, especially since hg edit/unedit may change the attributes
188 210 " of the file with no visible output.
189 211
190 212 echomsg "No output from HG command"
191 213 checktime
192 214 return -1
193 215 endif
194 216
195 if s:HGEditFile(resultBufferName, a:origBuffNR) == -1
217 if <SID>HGEditFile(resultBufferName, a:origBuffNR) == -1
196 218 return -1
197 219 endif
198 220
199 221 set buftype=nofile
200 222 set noswapfile
201 223 set filetype=
202 224
203 if s:HGGetOption("HGCommandDeleteOnHide", 0)
225 if <SID>HGGetOption("HGCommandDeleteOnHide", 0)
204 226 set bufhidden=delete
205 227 endif
206 228
207 229 silent 0put=hgOut
208 230
209 231 " The last command left a blank line at the end of the buffer. If the
210 232 " last line is folded (a side effect of the 'put') then the attempt to
211 233 " remove the blank line will kill the last fold.
212 234 "
213 235 " This could be fixed by explicitly detecting whether the last line is
214 236 " within a fold, but I prefer to simply unfold the result buffer altogether.
215 237
216 if has('folding')
217 normal zR
238 if has("folding")
239 setlocal nofoldenable
218 240 endif
219 241
220 242 $d
221 243 1
222 244
223 245 " Define the environment and execute user-defined hooks.
224 246
225 247 let b:HGSourceFile=fileName
226 248 let b:HGCommand=a:cmdName
227 249 if a:statusText != ""
228 250 let b:HGStatusText=a:statusText
229 251 endif
230 252
231 253 silent do HGCommand User HGBufferCreated
232 254 return bufnr("%")
233 255 endfunction
234 256
235 257 " Function: s:HGBufferCheck(hgBuffer) {{{2
236 258 " Attempts to locate the original file to which HG operations were applied
237 259 " for a given buffer.
238 260
239 261 function! s:HGBufferCheck(hgBuffer)
240 262 let origBuffer = getbufvar(a:hgBuffer, "HGOrigBuffNR")
241 263 if origBuffer
242 264 if bufexists(origBuffer)
243 265 return origBuffer
244 266 else
245 267 " Original buffer no longer exists.
246 return -1
268 return -1
247 269 endif
248 270 else
249 271 " No original buffer
250 272 return a:hgBuffer
251 273 endif
252 274 endfunction
253 275
254 276 " Function: s:HGCurrentBufferCheck() {{{2
255 277 " Attempts to locate the original file to which HG operations were applied
256 278 " for the current buffer.
257 279
258 280 function! s:HGCurrentBufferCheck()
259 return s:HGBufferCheck(bufnr("%"))
281 return <SID>HGBufferCheck(bufnr("%"))
260 282 endfunction
261 283
262 284 " Function: s:HGToggleDeleteOnHide() {{{2
263 285 " Toggles on and off the delete-on-hide behavior of HG buffers
264 286
265 287 function! s:HGToggleDeleteOnHide()
266 288 if exists("g:HGCommandDeleteOnHide")
267 289 unlet g:HGCommandDeleteOnHide
268 290 else
269 291 let g:HGCommandDeleteOnHide=1
270 292 endif
271 293 endfunction
272 294
273 295 " Function: s:HGDoCommand(hgcmd, cmdName, statusText) {{{2
274 296 " General skeleton for HG function execution.
275 297 " Returns: name of the new command buffer containing the command results
276 298
277 299 function! s:HGDoCommand(cmd, cmdName, statusText)
278 let hgBufferCheck=s:HGCurrentBufferCheck()
279 if hgBufferCheck == -1
300 let hgBufferCheck=<SID>HGCurrentBufferCheck()
301 if hgBufferCheck == -1
280 302 echo "Original buffer no longer exists, aborting."
281 303 return -1
282 304 endif
283 305
284 306 let fileName=bufname(hgBufferCheck)
285 307 if isdirectory(fileName)
286 308 let fileName=fileName . "/" . getline(".")
287 309 endif
288 let realFileName = fnamemodify(s:HGResolveLink(fileName), ':t')
289 let oldCwd=s:HGChangeToCurrentFileDir(fileName)
310 let realFileName = fnamemodify(<SID>HGResolveLink(fileName), ':t')
311 let oldCwd=<SID>HGChangeToCurrentFileDir(fileName)
290 312 try
291 313 " TODO
292 314 "if !filereadable('HG/Root')
293 315 "throw fileName . ' is not a HG-controlled file.'
294 316 "endif
295 317 let fullCmd = a:cmd . ' "' . realFileName . '"'
296 318 "echomsg "DEBUG".fullCmd
297 let resultBuffer=s:HGCreateCommandBuffer(fullCmd, a:cmdName, a:statusText, hgBufferCheck)
319 let resultBuffer=<SID>HGCreateCommandBuffer(fullCmd, a:cmdName, a:statusText, hgBufferCheck)
298 320 return resultBuffer
299 321 catch
300 322 echoerr v:exception
301 323 return -1
302 324 finally
303 325 execute 'cd' escape(oldCwd, ' ')
304 326 endtry
305 327 endfunction
306 328
307 329
308 330 " Function: s:HGGetStatusVars(revision, branch, repository) {{{2
309 331 "
310 332 " Obtains a HG revision number and branch name. The 'revisionVar',
311 333 " 'branchVar'and 'repositoryVar' arguments, if non-empty, contain the names of variables to hold
312 334 " the corresponding results.
313 335 "
314 336 " Returns: string to be exec'd that sets the multiple return values.
315 337
316 338 function! s:HGGetStatusVars(revisionVar, branchVar, repositoryVar)
317 let hgBufferCheck=s:HGCurrentBufferCheck()
339 let hgBufferCheck=<SID>HGCurrentBufferCheck()
318 340 "echomsg "DBG : in HGGetStatusVars"
319 if hgBufferCheck == -1
341 if hgBufferCheck == -1
320 342 return ""
321 343 endif
322 344 let fileName=bufname(hgBufferCheck)
323 let fileNameWithoutLink=s:HGResolveLink(fileName)
345 let fileNameWithoutLink=<SID>HGResolveLink(fileName)
324 346 let realFileName = fnamemodify(fileNameWithoutLink, ':t')
325 let oldCwd=s:HGChangeToCurrentFileDir(realFileName)
347 let oldCwd=<SID>HGChangeToCurrentFileDir(realFileName)
326 348 try
327 let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " root "
349 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " root "
328 350 let roottext=system(hgCommand)
329 351 " Suppress ending null char ! Does it work in window ?
330 352 let roottext=substitute(roottext,'^.*/\([^/\n\r]*\)\n\_.*$','\1','')
331 353 if match(getcwd()."/".fileNameWithoutLink, roottext) == -1
332 354 return ""
333 355 endif
334 356 let returnExpression = ""
335 357 if a:repositoryVar != ""
336 358 let returnExpression=returnExpression . " | let " . a:repositoryVar . "='" . roottext . "'"
337 359 endif
338 let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " status -mardui " . realFileName
360 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " status -mardui " . realFileName
339 361 let statustext=system(hgCommand)
340 362 if(v:shell_error)
341 363 return ""
342 364 endif
343 if match(statustext, '^[?I]') >= 0
365 if match(statustext, '^[?I]') >= 0
344 366 let revision="NEW"
345 elseif match(statustext, '^[R]') >= 0
367 elseif match(statustext, '^[R]') >= 0
346 368 let revision="REMOVED"
347 elseif match(statustext, '^[D]') >= 0
369 elseif match(statustext, '^[D]') >= 0
348 370 let revision="DELETED"
349 elseif match(statustext, '^[A]') >= 0
371 elseif match(statustext, '^[A]') >= 0
350 372 let revision="ADDED"
351 373 else
352 374 " The file is tracked, we can try to get is revision number
353 let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " parents -b "
375 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " parents -b "
354 376 let statustext=system(hgCommand)
355 377 if(v:shell_error)
356 return ""
378 return ""
357 379 endif
358 380 let revision=substitute(statustext, '^changeset:\s*\(\d\+\):.*\_$\_.*$', '\1', "")
359 381
360 382 if a:branchVar != "" && match(statustext, '^\_.*\_^branch:') >= 0
361 let branch=substitute(statustext, '^\_.*\_^branch:\s*\(\S\+\)\n\_.*$', '\1', "")
362 let returnExpression=returnExpression . " | let " . a:branchVar . "='" . branch . "'"
383 let branch=substitute(statustext, '^\_.*\_^branch:\s*\(\S\+\)\n\_.*$', '\1', "")
384 let returnExpression=returnExpression . " | let " . a:branchVar . "='" . branch . "'"
363 385 endif
364 386 endif
365 387 if (exists('revision'))
366 388 let returnExpression = "let " . a:revisionVar . "='" . revision . "' " . returnExpression
367 389 endif
368 390
369 391 return returnExpression
370 392 finally
371 393 execute 'cd' escape(oldCwd, ' ')
372 394 endtry
373 395 endfunction
374 396
375 397 " Function: s:HGSetupBuffer() {{{2
376 398 " Attempts to set the b:HGBranch, b:HGRevision and b:HGRepository variables.
377 399
378 400 function! s:HGSetupBuffer(...)
379 401 if (exists("b:HGBufferSetup") && b:HGBufferSetup && !exists('a:1'))
380 402 " This buffer is already set up.
381 403 return
382 404 endif
383 405
384 if !s:HGGetOption("HGCommandEnableBufferSetup", 0)
406 if !<SID>HGGetOption("HGCommandEnableBufferSetup", 0)
385 407 \ || @% == ""
386 408 \ || s:HGCommandEditFileRunning > 0
387 409 \ || exists("b:HGOrigBuffNR")
388 410 unlet! b:HGRevision
389 411 unlet! b:HGBranch
390 412 unlet! b:HGRepository
391 413 return
392 414 endif
393 415
394 416 if !filereadable(expand("%"))
395 417 return -1
396 418 endif
397 419
398 420 let revision=""
399 421 let branch=""
400 422 let repository=""
401 423
402 exec s:HGGetStatusVars('revision', 'branch', 'repository')
424 exec <SID>HGGetStatusVars('revision', 'branch', 'repository')
403 425 "echomsg "DBG ".revision."#".branch."#".repository
404 426 if revision != ""
405 427 let b:HGRevision=revision
406 428 else
407 429 unlet! b:HGRevision
408 430 endif
409 431 if branch != ""
410 432 let b:HGBranch=branch
411 433 else
412 434 unlet! b:HGBranch
413 435 endif
414 436 if repository != ""
415 437 let b:HGRepository=repository
416 438 else
417 439 unlet! b:HGRepository
418 440 endif
419 441 silent do HGCommand User HGBufferSetup
420 442 let b:HGBufferSetup=1
421 443 endfunction
422 444
423 445 " Function: s:HGMarkOrigBufferForSetup(hgbuffer) {{{2
424 446 " Resets the buffer setup state of the original buffer for a given HG buffer.
425 447 " Returns: The HG buffer number in a passthrough mode.
426 448
427 449 function! s:HGMarkOrigBufferForSetup(hgBuffer)
428 450 checktime
429 451 if a:hgBuffer != -1
430 let origBuffer = s:HGBufferCheck(a:hgBuffer)
452 let origBuffer = <SID>HGBufferCheck(a:hgBuffer)
431 453 "This should never not work, but I'm paranoid
432 454 if origBuffer != a:hgBuffer
433 455 call setbufvar(origBuffer, "HGBufferSetup", 0)
434 456 endif
435 457 else
436 458 "We are presumably in the original buffer
437 459 let b:HGBufferSetup = 0
438 460 "We do the setup now as now event will be triggered allowing it later.
439 call s:HGSetupBuffer()
461 call <SID>HGSetupBuffer()
440 462 endif
441 463 return a:hgBuffer
442 464 endfunction
443 465
444 466 " Function: s:HGOverrideOption(option, [value]) {{{2
445 467 " Provides a temporary override for the given HG option. If no value is
446 468 " passed, the override is disabled.
447 469
448 470 function! s:HGOverrideOption(option, ...)
449 471 if a:0 == 0
450 472 unlet! s:{a:option}Override
451 473 else
452 474 let s:{a:option}Override = a:1
453 475 endif
454 476 endfunction
455 477
456 478 " Function: s:HGWipeoutCommandBuffers() {{{2
457 479 " Clears all current HG buffers of the specified type for a given source.
458 480
459 481 function! s:HGWipeoutCommandBuffers(originalBuffer, hgCommand)
460 482 let buffer = 1
461 483 while buffer <= bufnr('$')
462 484 if getbufvar(buffer, 'HGOrigBuffNR') == a:originalBuffer
463 485 if getbufvar(buffer, 'HGCommand') == a:hgCommand
464 486 execute 'bw' buffer
465 487 endif
466 488 endif
467 489 let buffer = buffer + 1
468 490 endwhile
469 491 endfunction
470 492
471 493 " Function: s:HGInstallDocumentation(full_name, revision) {{{2
472 494 " Install help documentation.
473 495 " Arguments:
474 496 " full_name: Full name of this vim plugin script, including path name.
475 497 " revision: Revision of the vim script. #version# mark in the document file
476 498 " will be replaced with this string with 'v' prefix.
477 499 " Return:
478 500 " 1 if new document installed, 0 otherwise.
479 501 " Note: Cleaned and generalized by guo-peng Wen
480 502 "'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
481
482 function! s:HGInstallDocumentation(full_name, revision)
483 " Name of the document path based on the system we use:
484 if (has("unix"))
485 " On UNIX like system, using forward slash:
486 let l:slash_char = '/'
487 let l:mkdir_cmd = ':silent !mkdir -p '
488 else
489 " On M$ system, use backslash. Also mkdir syntax is different.
490 " This should only work on W2K and up.
491 let l:slash_char = '\'
492 let l:mkdir_cmd = ':silent !mkdir '
493 endif
494
495 let l:doc_path = l:slash_char . 'doc'
496 let l:doc_home = l:slash_char . '.vim' . l:slash_char . 'doc'
503 " Helper function to make mkdir as portable as possible
504 function! s:HGFlexiMkdir(dir)
505 if exists("*mkdir") " we can use Vim's own mkdir()
506 call mkdir(a:dir)
507 elseif !exists("+shellslash")
508 call system("mkdir -p '".a:dir."'")
509 else " M$
510 let l:ssl = &shellslash
511 try
512 set shellslash
513 " no single quotes?
514 call system('mkdir "'.a:dir.'"')
515 finally
516 let &shellslash = l:ssl
517 endtry
518 endif
519 endfunction
497 520
498 " Figure out document path based on full name of this script:
499 let l:vim_plugin_path = fnamemodify(a:full_name, ':h')
500 let l:vim_doc_path = fnamemodify(a:full_name, ':h:h') . l:doc_path
501 if (!(filewritable(l:vim_doc_path) == 2))
502 echomsg "Doc path: " . l:vim_doc_path
503 execute l:mkdir_cmd . '"' . l:vim_doc_path . '"'
504 if (!(filewritable(l:vim_doc_path) == 2))
505 " Try a default configuration in user home:
506 let l:vim_doc_path = expand("~") . l:doc_home
507 if (!(filewritable(l:vim_doc_path) == 2))
508 execute l:mkdir_cmd . '"' . l:vim_doc_path . '"'
509 if (!(filewritable(l:vim_doc_path) == 2))
510 " Put a warning:
511 echomsg "Unable to open documentation directory"
512 echomsg " type :help add-local-help for more informations."
513 return 0
514 endif
515 endif
521 function! s:HGInstallDocumentation(full_name)
522 " Figure out document path based on full name of this script:
523 let l:vim_doc_path = fnamemodify(a:full_name, ":h:h") . "/doc"
524 if filewritable(l:vim_doc_path) != 2
525 echomsg s:script_name . ": Trying to update docs at" l:vim_doc_path
526 silent! call <SID>HGFlexiMkdir(l:vim_doc_path)
527 if filewritable(l:vim_doc_path) != 2
528 " Try first item in 'runtimepath':
529 let l:vim_doc_path =
530 \ substitute(&runtimepath, '^\([^,]*\).*', '\1/doc', 'e')
531 if filewritable(l:vim_doc_path) != 2
532 echomsg s:script_name . ": Trying to update docs at" l:vim_doc_path
533 silent! call <SID>HGFlexiMkdir(l:vim_doc_path)
534 if filewritable(l:vim_doc_path) != 2
535 " Put a warning:
536 echomsg "Unable to open documentation directory"
537 echomsg " type `:help add-local-help' for more information."
538 return 0
516 539 endif
540 endif
517 541 endif
518
519 " Exit if we have problem to access the document directory:
520 if (!isdirectory(l:vim_plugin_path)
521 \ || !isdirectory(l:vim_doc_path)
522 \ || filewritable(l:vim_doc_path) != 2)
523 return 0
524 endif
525
526 " Full name of script and documentation file:
527 let l:script_name = fnamemodify(a:full_name, ':t')
528 let l:doc_name = fnamemodify(a:full_name, ':t:r') . '.txt'
529 let l:plugin_file = l:vim_plugin_path . l:slash_char . l:script_name
530 let l:doc_file = l:vim_doc_path . l:slash_char . l:doc_name
542 endif
531 543
532 " Bail out if document file is still up to date:
533 if (filereadable(l:doc_file) &&
534 \ getftime(l:plugin_file) < getftime(l:doc_file))
535 return 0
536 endif
544 " Full name of documentation file:
545 let l:doc_file =
546 \ l:vim_doc_path . "/" . s:script_name . ".txt"
547 " Bail out if document file is still up to date:
548 if filereadable(l:doc_file) &&
549 \ getftime(a:full_name) < getftime(l:doc_file)
550 return 0
551 endif
537 552
538 " Prepare window position restoring command:
539 if (strlen(@%))
540 let l:go_back = 'b ' . bufnr("%")
541 else
542 let l:go_back = 'enew!'
543 endif
544
545 " Create a new buffer & read in the plugin file (me):
546 setl nomodeline
547 exe 'enew!'
548 exe 'r ' . l:plugin_file
549
550 setl modeline
551 let l:buf = bufnr("%")
552 setl noswapfile modifiable
553
554 norm zR
555 norm gg
553 " temporary global settings
554 let l:lz = &lazyredraw
555 let l:hls = &hlsearch
556 set lazyredraw nohlsearch
557 " Create a new buffer & read in the plugin file (me):
558 1 new
559 setlocal noswapfile modifiable nomodeline
560 if has("folding")
561 setlocal nofoldenable
562 endif
563 silent execute "read" escape(a:full_name, " ")
564 let l:doc_buf = bufnr("%")
556 565
557 " Delete from first line to a line starts with
558 " === START_DOC
559 1,/^=\{3,}\s\+START_DOC\C/ d
560
561 " Delete from a line starts with
562 " === END_DOC
563 " to the end of the documents:
564 /^=\{3,}\s\+END_DOC\C/,$ d
565
566 " Remove fold marks:
567 %s/{\{3}[1-9]/ /
566 1
567 " Delete from first line to a line starts with
568 " === START_DOC
569 silent 1,/^=\{3,}\s\+START_DOC\C/ d
570 " Delete from a line starts with
571 " === END_DOC
572 " to the end of the documents:
573 silent /^=\{3,}\s\+END_DOC\C/,$ d
568 574
569 " Add modeline for help doc: the modeline string is mangled intentionally
570 " to avoid it be recognized by VIM:
571 call append(line('$'), '')
572 call append(line('$'), ' v' . 'im:tw=78:ts=8:ft=help:norl:')
573
574 " Replace revision:
575 exe "normal :1s/#version#/ v" . a:revision . "/\<CR>"
575 " Add modeline for help doc: the modeline string is mangled intentionally
576 " to avoid it be recognized by VIM:
577 call append(line("$"), "")
578 call append(line("$"), " v" . "im:tw=78:ts=8:ft=help:norl:")
576 579
577 " Save the help document:
578 exe 'w! ' . l:doc_file
579 exe l:go_back
580 exe 'bw ' . l:buf
580 " Replace revision:
581 silent execute "normal :1s/#version#/" . s:script_version . "/\<CR>"
582 " Save the help document and wipe out buffer:
583 silent execute "wq!" escape(l:doc_file, " ") "| bw" l:doc_buf
584 " Build help tags:
585 silent execute "helptags" l:vim_doc_path
581 586
582 " Build help tags:
583 exe 'helptags ' . l:vim_doc_path
584
585 return 1
587 let &hlsearch = l:hls
588 let &lazyredraw = l:lz
589 return 1
586 590 endfunction
587 591
588 592 " Section: Public functions {{{1
589 593
590 594 " Function: HGGetRevision() {{{2
591 595 " Global function for retrieving the current buffer's HG revision number.
592 596 " Returns: Revision number or an empty string if an error occurs.
593 597
594 598 function! HGGetRevision()
595 599 let revision=""
596 exec s:HGGetStatusVars('revision', '', '')
600 exec <SID>HGGetStatusVars('revision', '', '')
597 601 return revision
598 602 endfunction
599 603
600 604 " Function: HGDisableBufferSetup() {{{2
601 605 " Global function for deactivating the buffer autovariables.
602 606
603 607 function! HGDisableBufferSetup()
604 608 let g:HGCommandEnableBufferSetup=0
605 609 silent! augroup! HGCommandPlugin
606 610 endfunction
607 611
608 612 " Function: HGEnableBufferSetup() {{{2
609 613 " Global function for activating the buffer autovariables.
610 614
611 615 function! HGEnableBufferSetup()
612 616 let g:HGCommandEnableBufferSetup=1
613 617 augroup HGCommandPlugin
614 618 au!
615 au BufEnter * call s:HGSetupBuffer()
616 au BufWritePost * call s:HGSetupBuffer()
619 au BufEnter * call <SID>HGSetupBuffer()
620 au BufWritePost * call <SID>HGSetupBuffer()
617 621 " Force resetting up buffer on external file change (HG update)
618 au FileChangedShell * call s:HGSetupBuffer(1)
622 au FileChangedShell * call <SID>HGSetupBuffer(1)
619 623 augroup END
620 624
621 625 " Only auto-load if the plugin is fully loaded. This gives other plugins a
622 626 " chance to run.
623 627 if g:loaded_hgcommand == 2
624 call s:HGSetupBuffer()
628 call <SID>HGSetupBuffer()
625 629 endif
626 630 endfunction
627 631
628 632 " Function: HGGetStatusLine() {{{2
629 633 " Default (sample) status line entry for HG files. This is only useful if
630 634 " HG-managed buffer mode is on (see the HGCommandEnableBufferSetup variable
631 635 " for how to do this).
632 636
633 637 function! HGGetStatusLine()
634 638 if exists('b:HGSourceFile')
635 639 " This is a result buffer
636 640 let value='[' . b:HGCommand . ' ' . b:HGSourceFile
637 641 if exists('b:HGStatusText')
638 642 let value=value . ' ' . b:HGStatusText
639 643 endif
640 644 let value = value . ']'
641 645 return value
642 646 endif
643 647
644 648 if exists('b:HGRevision')
645 649 \ && b:HGRevision != ''
646 650 \ && exists('b:HGRepository')
647 651 \ && b:HGRepository != ''
648 652 \ && exists('g:HGCommandEnableBufferSetup')
649 653 \ && g:HGCommandEnableBufferSetup
650 654 if !exists('b:HGBranch')
651 655 let l:branch=''
652 656 else
653 657 let l:branch=b:HGBranch
654 658 endif
655 659 return '[HG ' . b:HGRepository . '/' . l:branch .'/' . b:HGRevision . ']'
656 660 else
657 661 return ''
658 662 endif
659 663 endfunction
660 664
661 665 " Section: HG command functions {{{1
662 666
663 667 " Function: s:HGAdd() {{{2
664 668 function! s:HGAdd()
665 return s:HGMarkOrigBufferForSetup(s:HGDoCommand('add', 'hgadd', ''))
669 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('add', 'hgadd', ''))
666 670 endfunction
667 671
668 672 " Function: s:HGAnnotate(...) {{{2
669 673 function! s:HGAnnotate(...)
670 674 if a:0 == 0
671 675 if &filetype == "HGAnnotate"
672 676 " This is a HGAnnotate buffer. Perform annotation of the version
673 677 " indicated by the current line.
674 678 let revision = substitute(getline("."),'\(^[0-9]*\):.*','\1','')
675 if s:HGGetOption('HGCommandAnnotateParent', 0) != 0 && revision > 0
679 if <SID>HGGetOption('HGCommandAnnotateParent', 0) != 0 && revision > 0
676 680 let revision = revision - 1
677 681 endif
678 682 else
679 683 let revision=HGGetRevision()
680 684 if revision == ""
681 685 echoerr "Unable to obtain HG version information."
682 686 return -1
683 687 endif
684 688 endif
685 689 else
686 690 let revision=a:1
687 691 endif
688 692
689 693 if revision == "NEW"
690 694 echo "No annotatation available for new file."
691 695 return -1
692 696 endif
693 697
694 let resultBuffer=s:HGDoCommand('annotate -ndu -r ' . revision, 'hgannotate', revision)
698 let resultBuffer=<SID>HGDoCommand('annotate -ndu -r ' . revision, 'hgannotate', revision)
695 699 "echomsg "DBG: ".resultBuffer
696 700 if resultBuffer != -1
697 701 set filetype=HGAnnotate
698 702 endif
699 703
700 704 return resultBuffer
701 705 endfunction
702 706
703 707 " Function: s:HGCommit() {{{2
704 708 function! s:HGCommit(...)
705 709 " Handle the commit message being specified. If a message is supplied, it
706 710 " is used; if bang is supplied, an empty message is used; otherwise, the
707 711 " user is provided a buffer from which to edit the commit message.
708 712 if a:2 != "" || a:1 == "!"
709 return s:HGMarkOrigBufferForSetup(s:HGDoCommand('commit -m "' . a:2 . '"', 'hgcommit', ''))
713 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('commit -m "' . a:2 . '"', 'hgcommit', ''))
710 714 endif
711 715
712 let hgBufferCheck=s:HGCurrentBufferCheck()
716 let hgBufferCheck=<SID>HGCurrentBufferCheck()
713 717 if hgBufferCheck == -1
714 718 echo "Original buffer no longer exists, aborting."
715 719 return -1
716 720 endif
717 721
718 722 " Protect against windows' backslashes in paths. They confuse exec'd
719 723 " commands.
720 724
721 725 let shellSlashBak = &shellslash
722 726 try
723 727 set shellslash
724 728
725 729 let messageFileName = tempname()
726 730
727 731 let fileName=bufname(hgBufferCheck)
728 let realFilePath=s:HGResolveLink(fileName)
732 let realFilePath=<SID>HGResolveLink(fileName)
729 733 let newCwd=fnamemodify(realFilePath, ':h')
730 734 if strlen(newCwd) == 0
731 735 " Account for autochdir being in effect, which will make this blank, but
732 736 " we know we'll be in the current directory for the original file.
733 737 let newCwd = getcwd()
734 738 endif
735 739
736 740 let realFileName=fnamemodify(realFilePath, ':t')
737 741
738 if s:HGEditFile(messageFileName, hgBufferCheck) == -1
742 if <SID>HGEditFile(messageFileName, hgBufferCheck) == -1
739 743 return
740 744 endif
741 745
742 746 " Protect against case and backslash issues in Windows.
743 747 let autoPattern = '\c' . messageFileName
744 748
745 749 " Ensure existance of group
746 750 augroup HGCommit
747 751 augroup END
748 752
749 753 execute 'au HGCommit BufDelete' autoPattern 'call delete("' . messageFileName . '")'
750 754 execute 'au HGCommit BufDelete' autoPattern 'au! HGCommit * ' autoPattern
751 755
752 756 " Create a commit mapping. The mapping must clear all autocommands in case
753 757 " it is invoked when HGCommandCommitOnWrite is active, as well as to not
754 758 " invoke the buffer deletion autocommand.
755 759
756 760 execute 'nnoremap <silent> <buffer> <Plug>HGCommit '.
757 761 \ ':au! HGCommit * ' . autoPattern . '<CR>'.
758 762 \ ':g/^HG:/d<CR>'.
759 763 \ ':update<CR>'.
760 764 \ ':call <SID>HGFinishCommit("' . messageFileName . '",' .
761 765 \ '"' . newCwd . '",' .
762 766 \ '"' . realFileName . '",' .
763 767 \ hgBufferCheck . ')<CR>'
764 768
765 769 silent 0put ='HG: ----------------------------------------------------------------------'
766 770 silent put =\"HG: Enter Log. Lines beginning with `HG:' are removed automatically\"
767 771 silent put ='HG: Type <leader>cc (or your own <Plug>HGCommit mapping)'
768 772
769 if s:HGGetOption('HGCommandCommitOnWrite', 1) == 1
773 if <SID>HGGetOption('HGCommandCommitOnWrite', 1) == 1
770 774 execute 'au HGCommit BufWritePre' autoPattern 'g/^HG:/d'
771 execute 'au HGCommit BufWritePost' autoPattern 'call s:HGFinishCommit("' . messageFileName . '", "' . newCwd . '", "' . realFileName . '", ' . hgBufferCheck . ') | au! * ' autoPattern
775 execute 'au HGCommit BufWritePost' autoPattern 'call <SID>HGFinishCommit("' . messageFileName . '", "' . newCwd . '", "' . realFileName . '", ' . hgBufferCheck . ') | au! * ' autoPattern
772 776 silent put ='HG: or write this buffer'
773 777 endif
774 778
775 779 silent put ='HG: to finish this commit operation'
776 780 silent put ='HG: ----------------------------------------------------------------------'
777 781 $
778 782 let b:HGSourceFile=fileName
779 783 let b:HGCommand='HGCommit'
780 784 set filetype=hg
781 785 finally
782 786 let &shellslash = shellSlashBak
783 787 endtry
784 788
785 789 endfunction
786 790
787 791 " Function: s:HGDiff(...) {{{2
788 792 function! s:HGDiff(...)
789 793 if a:0 == 1
790 794 let revOptions = '-r' . a:1
791 795 let caption = a:1 . ' -> current'
792 796 elseif a:0 == 2
793 797 let revOptions = '-r' . a:1 . ' -r' . a:2
794 798 let caption = a:1 . ' -> ' . a:2
795 799 else
796 800 let revOptions = ''
797 801 let caption = ''
798 802 endif
799 803
800 let hgdiffopt=s:HGGetOption('HGCommandDiffOpt', 'w')
804 let hgdiffopt=<SID>HGGetOption('HGCommandDiffOpt', 'w')
801 805
802 806 if hgdiffopt == ""
803 807 let diffoptionstring=""
804 808 else
805 809 let diffoptionstring=" -" . hgdiffopt . " "
806 810 endif
807 811
808 let resultBuffer = s:HGDoCommand('diff ' . diffoptionstring . revOptions , 'hgdiff', caption)
809 if resultBuffer != -1
812 let resultBuffer = <SID>HGDoCommand('diff ' . diffoptionstring . revOptions , 'hgdiff', caption)
813 if resultBuffer != -1
810 814 set filetype=diff
811 815 endif
812 816 return resultBuffer
813 817 endfunction
814 818
815 819
816 820 " Function: s:HGGotoOriginal(["!]) {{{2
817 821 function! s:HGGotoOriginal(...)
818 let origBuffNR = s:HGCurrentBufferCheck()
822 let origBuffNR = <SID>HGCurrentBufferCheck()
819 823 if origBuffNR > 0
820 824 let origWinNR = bufwinnr(origBuffNR)
821 825 if origWinNR == -1
822 826 execute 'buffer' origBuffNR
823 827 else
824 828 execute origWinNR . 'wincmd w'
825 829 endif
826 830 if a:0 == 1
827 831 if a:1 == "!"
828 832 let buffnr = 1
829 833 let buffmaxnr = bufnr("$")
830 834 while buffnr <= buffmaxnr
831 835 if getbufvar(buffnr, "HGOrigBuffNR") == origBuffNR
832 836 execute "bw" buffnr
833 837 endif
834 838 let buffnr = buffnr + 1
835 839 endwhile
836 840 endif
837 841 endif
838 842 endif
839 843 endfunction
840 844
841 845 " Function: s:HGFinishCommit(messageFile, targetDir, targetFile) {{{2
842 846 function! s:HGFinishCommit(messageFile, targetDir, targetFile, origBuffNR)
843 847 if filereadable(a:messageFile)
844 848 let oldCwd=getcwd()
845 849 if strlen(a:targetDir) > 0
846 850 execute 'cd' escape(a:targetDir, ' ')
847 851 endif
848 let resultBuffer=s:HGCreateCommandBuffer('commit -l "' . a:messageFile . '" "'. a:targetFile . '"', 'hgcommit', '', a:origBuffNR)
852 let resultBuffer=<SID>HGCreateCommandBuffer('commit -l "' . a:messageFile . '" "'. a:targetFile . '"', 'hgcommit', '', a:origBuffNR)
849 853 execute 'cd' escape(oldCwd, ' ')
850 854 execute 'bw' escape(a:messageFile, ' *?\')
851 855 silent execute 'call delete("' . a:messageFile . '")'
852 return s:HGMarkOrigBufferForSetup(resultBuffer)
856 return <SID>HGMarkOrigBufferForSetup(resultBuffer)
853 857 else
854 858 echoerr "Can't read message file; no commit is possible."
855 859 return -1
856 860 endif
857 861 endfunction
858 862
859 863 " Function: s:HGLog() {{{2
860 864 function! s:HGLog(...)
861 865 if a:0 == 0
862 866 let versionOption = ""
863 867 let caption = ''
864 868 else
865 869 let versionOption=" -r" . a:1
866 870 let caption = a:1
867 871 endif
868 872
869 let resultBuffer=s:HGDoCommand('log' . versionOption, 'hglog', caption)
873 let resultBuffer=<SID>HGDoCommand('log' . versionOption, 'hglog', caption)
870 874 if resultBuffer != ""
871 875 set filetype=rcslog
872 876 endif
873 877 return resultBuffer
874 878 endfunction
875 879
876 880 " Function: s:HGRevert() {{{2
877 881 function! s:HGRevert()
878 return s:HGMarkOrigBufferForSetup(s:HGDoCommand('revert', 'hgrevert', ''))
882 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('revert', 'hgrevert', ''))
879 883 endfunction
880 884
881 885 " Function: s:HGReview(...) {{{2
882 886 function! s:HGReview(...)
883 887 if a:0 == 0
884 888 let versiontag=""
885 if s:HGGetOption('HGCommandInteractive', 0)
889 if <SID>HGGetOption('HGCommandInteractive', 0)
886 890 let versiontag=input('Revision: ')
887 891 endif
888 892 if versiontag == ""
889 893 let versiontag="(current)"
890 894 let versionOption=""
891 895 else
892 896 let versionOption=" -r " . versiontag . " "
893 897 endif
894 898 else
895 899 let versiontag=a:1
896 900 let versionOption=" -r " . versiontag . " "
897 901 endif
898 902
899 let resultBuffer = s:HGDoCommand('cat' . versionOption, 'hgreview', versiontag)
903 let resultBuffer = <SID>HGDoCommand('cat' . versionOption, 'hgreview', versiontag)
900 904 if resultBuffer > 0
901 905 let &filetype=getbufvar(b:HGOrigBuffNR, '&filetype')
902 906 endif
903 907
904 908 return resultBuffer
905 909 endfunction
906 910
907 911 " Function: s:HGStatus() {{{2
908 912 function! s:HGStatus()
909 return s:HGDoCommand('status', 'hgstatus', '')
913 return <SID>HGDoCommand('status', 'hgstatus', '')
910 914 endfunction
911 915
912 916
913 917 " Function: s:HGUpdate() {{{2
914 918 function! s:HGUpdate()
915 return s:HGMarkOrigBufferForSetup(s:HGDoCommand('update', 'update', ''))
919 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('update', 'update', ''))
916 920 endfunction
917 921
918 922 " Function: s:HGVimDiff(...) {{{2
919 923 function! s:HGVimDiff(...)
920 let originalBuffer = s:HGCurrentBufferCheck()
924 let originalBuffer = <SID>HGCurrentBufferCheck()
921 925 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
922 926 try
923 927 " If there's already a VimDiff'ed window, restore it.
924 928 " There may only be one HGVimDiff original window at a time.
925 929
926 930 if exists("s:vimDiffSourceBuffer") && s:vimDiffSourceBuffer != originalBuffer
927 931 " Clear the existing vimdiff setup by removing the result buffers.
928 call s:HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff')
932 call <SID>HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff')
929 933 endif
930 934
931 935 " Split and diff
932 936 if(a:0 == 2)
933 937 " Reset the vimdiff system, as 2 explicit versions were provided.
934 938 if exists('s:vimDiffSourceBuffer')
935 call s:HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff')
939 call <SID>HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff')
936 940 endif
937 let resultBuffer = s:HGReview(a:1)
941 let resultBuffer = <SID>HGReview(a:1)
938 942 if resultBuffer < 0
939 943 echomsg "Can't open HG revision " . a:1
940 944 return resultBuffer
941 945 endif
942 946 let b:HGCommand = 'vimdiff'
943 947 diffthis
944 948 let s:vimDiffBufferCount = 1
945 949 let s:vimDiffScratchList = '{'. resultBuffer . '}'
946 950 " If no split method is defined, cheat, and set it to vertical.
947 951 try
948 call s:HGOverrideOption('HGCommandSplit', s:HGGetOption('HGCommandDiffSplit', s:HGGetOption('HGCommandSplit', 'vertical')))
949 let resultBuffer=s:HGReview(a:2)
952 call <SID>HGOverrideOption('HGCommandSplit', <SID>HGGetOption('HGCommandDiffSplit', <SID>HGGetOption('HGCommandSplit', 'vertical')))
953 let resultBuffer=<SID>HGReview(a:2)
950 954 finally
951 call s:HGOverrideOption('HGCommandSplit')
955 call <SID>HGOverrideOption('HGCommandSplit')
952 956 endtry
953 957 if resultBuffer < 0
954 958 echomsg "Can't open HG revision " . a:1
955 959 return resultBuffer
956 960 endif
957 961 let b:HGCommand = 'vimdiff'
958 962 diffthis
959 963 let s:vimDiffBufferCount = 2
960 964 let s:vimDiffScratchList = s:vimDiffScratchList . '{'. resultBuffer . '}'
961 965 else
962 966 " Add new buffer
963 967 try
964 968 " Force splitting behavior, otherwise why use vimdiff?
965 call s:HGOverrideOption("HGCommandEdit", "split")
966 call s:HGOverrideOption("HGCommandSplit", s:HGGetOption('HGCommandDiffSplit', s:HGGetOption('HGCommandSplit', 'vertical')))
969 call <SID>HGOverrideOption("HGCommandEdit", "split")
970 call <SID>HGOverrideOption("HGCommandSplit", <SID>HGGetOption('HGCommandDiffSplit', <SID>HGGetOption('HGCommandSplit', 'vertical')))
967 971 if(a:0 == 0)
968 let resultBuffer=s:HGReview()
972 let resultBuffer=<SID>HGReview()
969 973 else
970 let resultBuffer=s:HGReview(a:1)
974 let resultBuffer=<SID>HGReview(a:1)
971 975 endif
972 976 finally
973 call s:HGOverrideOption("HGCommandEdit")
974 call s:HGOverrideOption("HGCommandSplit")
977 call <SID>HGOverrideOption("HGCommandEdit")
978 call <SID>HGOverrideOption("HGCommandSplit")
975 979 endtry
976 980 if resultBuffer < 0
977 981 echomsg "Can't open current HG revision"
978 982 return resultBuffer
979 983 endif
980 984 let b:HGCommand = 'vimdiff'
981 985 diffthis
982 986
983 987 if !exists('s:vimDiffBufferCount')
984 988 " New instance of vimdiff.
985 989 let s:vimDiffBufferCount = 2
986 990 let s:vimDiffScratchList = '{' . resultBuffer . '}'
987 991
988 992 " This could have been invoked on a HG result buffer, not the
989 993 " original buffer.
990 994 wincmd W
991 995 execute 'buffer' originalBuffer
992 996 " Store info for later original buffer restore
993 let s:vimDiffRestoreCmd =
997 let s:vimDiffRestoreCmd =
994 998 \ "call setbufvar(".originalBuffer.", \"&diff\", ".getbufvar(originalBuffer, '&diff').")"
995 999 \ . "|call setbufvar(".originalBuffer.", \"&foldcolumn\", ".getbufvar(originalBuffer, '&foldcolumn').")"
996 1000 \ . "|call setbufvar(".originalBuffer.", \"&foldenable\", ".getbufvar(originalBuffer, '&foldenable').")"
997 1001 \ . "|call setbufvar(".originalBuffer.", \"&foldmethod\", '".getbufvar(originalBuffer, '&foldmethod')."')"
998 1002 \ . "|call setbufvar(".originalBuffer.", \"&scrollbind\", ".getbufvar(originalBuffer, '&scrollbind').")"
999 1003 \ . "|call setbufvar(".originalBuffer.", \"&wrap\", ".getbufvar(originalBuffer, '&wrap').")"
1000 \ . "|if &foldmethod=='manual'|execute 'normal zE'|endif"
1004 \ . "|if &foldmethod=='manual'|execute 'normal! zE'|endif"
1001 1005 diffthis
1002 1006 wincmd w
1003 1007 else
1004 1008 " Adding a window to an existing vimdiff
1005 1009 let s:vimDiffBufferCount = s:vimDiffBufferCount + 1
1006 1010 let s:vimDiffScratchList = s:vimDiffScratchList . '{' . resultBuffer . '}'
1007 1011 endif
1008 1012 endif
1009 1013
1010 1014 let s:vimDiffSourceBuffer = originalBuffer
1011 1015
1012 1016 " Avoid executing the modeline in the current buffer after the autocommand.
1013 1017
1014 1018 let currentBuffer = bufnr('%')
1015 1019 let saveModeline = getbufvar(currentBuffer, '&modeline')
1016 1020 try
1017 1021 call setbufvar(currentBuffer, '&modeline', 0)
1018 1022 silent do HGCommand User HGVimDiffFinish
1019 1023 finally
1020 1024 call setbufvar(currentBuffer, '&modeline', saveModeline)
1021 1025 endtry
1022 1026 return resultBuffer
1023 1027 finally
1024 1028 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
1025 1029 endtry
1026 1030 endfunction
1027 1031
1028 1032 " Section: Command definitions {{{1
1029 1033 " Section: Primary commands {{{2
1030 com! HGAdd call s:HGAdd()
1031 com! -nargs=? HGAnnotate call s:HGAnnotate(<f-args>)
1032 com! -bang -nargs=? HGCommit call s:HGCommit(<q-bang>, <q-args>)
1033 com! -nargs=* HGDiff call s:HGDiff(<f-args>)
1034 com! -bang HGGotoOriginal call s:HGGotoOriginal(<q-bang>)
1035 com! -nargs=? HGLog call s:HGLog(<f-args>)
1036 com! HGRevert call s:HGRevert()
1037 com! -nargs=? HGReview call s:HGReview(<f-args>)
1038 com! HGStatus call s:HGStatus()
1039 com! HGUpdate call s:HGUpdate()
1040 com! -nargs=* HGVimDiff call s:HGVimDiff(<f-args>)
1034 com! HGAdd call <SID>HGAdd()
1035 com! -nargs=? HGAnnotate call <SID>HGAnnotate(<f-args>)
1036 com! -bang -nargs=? HGCommit call <SID>HGCommit(<q-bang>, <q-args>)
1037 com! -nargs=* HGDiff call <SID>HGDiff(<f-args>)
1038 com! -bang HGGotoOriginal call <SID>HGGotoOriginal(<q-bang>)
1039 com! -nargs=? HGLog call <SID>HGLog(<f-args>)
1040 com! HGRevert call <SID>HGRevert()
1041 com! -nargs=? HGReview call <SID>HGReview(<f-args>)
1042 com! HGStatus call <SID>HGStatus()
1043 com! HGUpdate call <SID>HGUpdate()
1044 com! -nargs=* HGVimDiff call <SID>HGVimDiff(<f-args>)
1041 1045
1042 1046 " Section: HG buffer management commands {{{2
1043 1047 com! HGDisableBufferSetup call HGDisableBufferSetup()
1044 1048 com! HGEnableBufferSetup call HGEnableBufferSetup()
1045 1049
1046 1050 " Allow reloading hgcommand.vim
1047 1051 com! HGReload unlet! loaded_hgcommand | runtime plugin/hgcommand.vim
1048 1052
1049 1053 " Section: Plugin command mappings {{{1
1050 1054 nnoremap <silent> <Plug>HGAdd :HGAdd<CR>
1051 1055 nnoremap <silent> <Plug>HGAnnotate :HGAnnotate<CR>
1052 1056 nnoremap <silent> <Plug>HGCommit :HGCommit<CR>
1053 1057 nnoremap <silent> <Plug>HGDiff :HGDiff<CR>
1054 1058 nnoremap <silent> <Plug>HGGotoOriginal :HGGotoOriginal<CR>
1055 1059 nnoremap <silent> <Plug>HGClearAndGotoOriginal :HGGotoOriginal!<CR>
1056 1060 nnoremap <silent> <Plug>HGLog :HGLog<CR>
1057 1061 nnoremap <silent> <Plug>HGRevert :HGRevert<CR>
1058 1062 nnoremap <silent> <Plug>HGReview :HGReview<CR>
1059 1063 nnoremap <silent> <Plug>HGStatus :HGStatus<CR>
1060 1064 nnoremap <silent> <Plug>HGUpdate :HGUpdate<CR>
1061 1065 nnoremap <silent> <Plug>HGVimDiff :HGVimDiff<CR>
1062 1066
1063 1067 " Section: Default mappings {{{1
1064 1068 if !hasmapto('<Plug>HGAdd')
1065 1069 nmap <unique> <Leader>hga <Plug>HGAdd
1066 1070 endif
1067 1071 if !hasmapto('<Plug>HGAnnotate')
1068 1072 nmap <unique> <Leader>hgn <Plug>HGAnnotate
1069 1073 endif
1070 1074 if !hasmapto('<Plug>HGClearAndGotoOriginal')
1071 1075 nmap <unique> <Leader>hgG <Plug>HGClearAndGotoOriginal
1072 1076 endif
1073 1077 if !hasmapto('<Plug>HGCommit')
1074 1078 nmap <unique> <Leader>hgc <Plug>HGCommit
1075 1079 endif
1076 1080 if !hasmapto('<Plug>HGDiff')
1077 1081 nmap <unique> <Leader>hgd <Plug>HGDiff
1078 1082 endif
1079 1083 if !hasmapto('<Plug>HGGotoOriginal')
1080 1084 nmap <unique> <Leader>hgg <Plug>HGGotoOriginal
1081 1085 endif
1082 1086 if !hasmapto('<Plug>HGLog')
1083 1087 nmap <unique> <Leader>hgl <Plug>HGLog
1084 1088 endif
1085 1089 if !hasmapto('<Plug>HGRevert')
1086 1090 nmap <unique> <Leader>hgq <Plug>HGRevert
1087 1091 endif
1088 1092 if !hasmapto('<Plug>HGReview')
1089 1093 nmap <unique> <Leader>hgr <Plug>HGReview
1090 1094 endif
1091 1095 if !hasmapto('<Plug>HGStatus')
1092 1096 nmap <unique> <Leader>hgs <Plug>HGStatus
1093 1097 endif
1094 1098 if !hasmapto('<Plug>HGUpdate')
1095 1099 nmap <unique> <Leader>hgu <Plug>HGUpdate
1096 1100 endif
1097 1101 if !hasmapto('<Plug>HGVimDiff')
1098 1102 nmap <unique> <Leader>hgv <Plug>HGVimDiff
1099 1103 endif
1100 1104
1101 1105 " Section: Menu items {{{1
1102 1106 silent! aunmenu Plugin.HG
1103 1107 amenu <silent> &Plugin.HG.&Add <Plug>HGAdd
1104 1108 amenu <silent> &Plugin.HG.A&nnotate <Plug>HGAnnotate
1105 1109 amenu <silent> &Plugin.HG.&Commit <Plug>HGCommit
1106 1110 amenu <silent> &Plugin.HG.&Diff <Plug>HGDiff
1107 1111 amenu <silent> &Plugin.HG.&Log <Plug>HGLog
1108 1112 amenu <silent> &Plugin.HG.Revert <Plug>HGRevert
1109 1113 amenu <silent> &Plugin.HG.&Review <Plug>HGReview
1110 1114 amenu <silent> &Plugin.HG.&Status <Plug>HGStatus
1111 1115 amenu <silent> &Plugin.HG.&Update <Plug>HGUpdate
1112 1116 amenu <silent> &Plugin.HG.&VimDiff <Plug>HGVimDiff
1113 1117
1114 1118 " Section: Autocommands to restore vimdiff state {{{1
1115 1119 function! s:HGVimDiffRestore(vimDiffBuff)
1116 1120 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
1117 1121 try
1118 1122 if exists("s:vimDiffSourceBuffer")
1119 1123 if a:vimDiffBuff == s:vimDiffSourceBuffer
1120 1124 " Original file is being removed.
1121 1125 unlet! s:vimDiffSourceBuffer
1122 1126 unlet! s:vimDiffBufferCount
1123 1127 unlet! s:vimDiffRestoreCmd
1124 1128 unlet! s:vimDiffScratchList
1125 1129 elseif match(s:vimDiffScratchList, '{' . a:vimDiffBuff . '}') >= 0
1126 1130 let s:vimDiffScratchList = substitute(s:vimDiffScratchList, '{' . a:vimDiffBuff . '}', '', '')
1127 1131 let s:vimDiffBufferCount = s:vimDiffBufferCount - 1
1128 1132 if s:vimDiffBufferCount == 1 && exists('s:vimDiffRestoreCmd')
1129 1133 " All scratch buffers are gone, reset the original.
1130 1134 " Only restore if the source buffer is still in Diff mode
1131 1135
1132 1136 let sourceWinNR=bufwinnr(s:vimDiffSourceBuffer)
1133 1137 if sourceWinNR != -1
1134 1138 " The buffer is visible in at least one window
1135 1139 let currentWinNR = winnr()
1136 1140 while winbufnr(sourceWinNR) != -1
1137 1141 if winbufnr(sourceWinNR) == s:vimDiffSourceBuffer
1138 1142 execute sourceWinNR . 'wincmd w'
1139 1143 if getwinvar('', "&diff")
1140 1144 execute s:vimDiffRestoreCmd
1141 1145 endif
1142 1146 endif
1143 1147 let sourceWinNR = sourceWinNR + 1
1144 1148 endwhile
1145 1149 execute currentWinNR . 'wincmd w'
1146 1150 else
1147 1151 " The buffer is hidden. It must be visible in order to set the
1148 1152 " diff option.
1149 1153 let currentBufNR = bufnr('')
1150 1154 execute "hide buffer" s:vimDiffSourceBuffer
1151 1155 if getwinvar('', "&diff")
1152 1156 execute s:vimDiffRestoreCmd
1153 1157 endif
1154 1158 execute "hide buffer" currentBufNR
1155 1159 endif
1156 1160
1157 1161 unlet s:vimDiffRestoreCmd
1158 1162 unlet s:vimDiffSourceBuffer
1159 1163 unlet s:vimDiffBufferCount
1160 1164 unlet s:vimDiffScratchList
1161 1165 elseif s:vimDiffBufferCount == 0
1162 1166 " All buffers are gone.
1163 1167 unlet s:vimDiffSourceBuffer
1164 1168 unlet s:vimDiffBufferCount
1165 1169 unlet s:vimDiffScratchList
1166 1170 endif
1167 1171 endif
1168 1172 endif
1169 1173 finally
1170 1174 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
1171 1175 endtry
1172 1176 endfunction
1173 1177
1174 1178 augroup HGVimDiffRestore
1175 1179 au!
1176 au BufUnload * call s:HGVimDiffRestore(expand("<abuf>"))
1180 au BufUnload * call <SID>HGVimDiffRestore(expand("<abuf>"))
1177 1181 augroup END
1178 1182
1179 1183 " Section: Optional activation of buffer management {{{1
1180 1184
1181 1185 if s:HGGetOption('HGCommandEnableBufferSetup', 1)
1182 1186 call HGEnableBufferSetup()
1183 1187 endif
1184 1188
1185 1189 " Section: Doc installation {{{1
1186 "
1187 let s:revision="0.1"
1188 silent! let s:install_status =
1189 \ s:HGInstallDocumentation(expand('<sfile>:p'), s:revision)
1190 if (s:install_status == 1)
1191 echom expand("<sfile>:t:r") . ' v' . s:revision .
1192 \ ': Help-documentation installed.'
1193 endif
1194 1190
1191 if <SID>HGInstallDocumentation(expand("<sfile>:p"))
1192 echomsg s:script_name s:script_version . ": updated documentation"
1193 endif
1195 1194
1196 1195 " Section: Plugin completion {{{1
1197 1196
1197 " delete one-time vars and functions
1198 delfunction <SID>HGInstallDocumentation
1199 delfunction <SID>HGFlexiMkdir
1200 delfunction <SID>HGCleanupOnFailure
1201 unlet s:script_version s:script_name
1202
1198 1203 let loaded_hgcommand=2
1199 1204 silent do HGCommand User HGPluginFinish
1205
1206 let &cpo = s:save_cpo
1207 unlet s:save_cpo
1200 1208 " vim:se expandtab sts=2 sw=2:
1201 1209 finish
1202 1210
1203 1211 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1204 1212 " Section: Documentation content {{{1
1205 1213 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1206 1214 === START_DOC
1207 1215 *hgcommand.txt* Mercurial vim integration #version#
1208 1216
1209 1217
1210 1218 HGCOMMAND REFERENCE MANUAL~
1211 1219
1212 1220
1213 1221 Author: Mathieu Clabaut <mathieu.clabaut@gmail.com>
1214 1222 Credits: Bob Hiestand <bob.hiestand@gmail.com>
1215 1223 Mercurial: http://www.selenic.com/mercurial
1216 1224 Mercurial (noted Hg) is a fast, lightweight Source Control Management
1217 1225 system designed for efficient handling of very large distributed projects.
1218 1226
1219 1227 ==============================================================================
1220 1228 1. Contents *hgcommand-contents*
1221 1229
1222 1230 Installation : |hgcommand-install|
1223 1231 HGCommand Intro : |hgcommand|
1224 1232 HGCommand Manual : |hgcommand-manual|
1225 1233 Customization : |hgcommand-customize|
1226 1234 Bugs : |hgcommand-bugs|
1227 1235
1228 1236 ==============================================================================
1229 1237 2. HGCommand Installation *hgcommand-install*
1230 1238
1231 In order to install the plugin, place the hgcommand.vim file into a plugin'
1232 directory in your runtime path (please see |add-global-plugin| and
1239 In order to install the plugin, place the hgcommand.vim file into a plugin'
1240 directory in your runtime path (please see |add-global-plugin| and
1233 1241 |'runtimepath'|.
1234 1242
1235 HGCommand may be customized by setting variables, creating maps, and
1243 HGCommand may be customized by setting variables, creating maps, and
1236 1244 specifying event handlers. Please see |hgcommand-customize| for more
1237 1245 details.
1238 1246
1239 1247 *hgcommand-auto-help*
1240 The help file is automagically generated when the |hgcommand| script is
1248 The help file is automagically generated when the |hgcommand| script is
1241 1249 loaded for the first time.
1242 1250
1243 1251 ==============================================================================
1244 1252
1245 1253 3. HGCommand Intro *hgcommand*
1246 1254 *hgcommand-intro*
1247 1255
1248 The HGCommand plugin provides global ex commands for manipulating
1249 HG-controlled source files. In general, each command operates on the
1250 current buffer and accomplishes a separate hg function, such as update,
1256 The HGCommand plugin provides global ex commands for manipulating
1257 HG-controlled source files. In general, each command operates on the
1258 current buffer and accomplishes a separate hg function, such as update,
1251 1259 commit, log, and others (please see |hgcommand-commands| for a list of all
1252 available commands). The results of each operation are displayed in a
1253 scratch buffer. Several buffer variables are defined for those scratch
1260 available commands). The results of each operation are displayed in a
1261 scratch buffer. Several buffer variables are defined for those scratch
1254 1262 buffers (please see |hgcommand-buffer-variables|).
1255 1263
1256 The notion of "current file" means either the current buffer, or, in the
1264 The notion of "current file" means either the current buffer, or, in the
1257 1265 case of a directory buffer, the file on the current line within the buffer.
1258 1266
1259 For convenience, any HGCommand invoked on a HGCommand scratch buffer acts
1260 as though it was invoked on the original file and splits the screen so that
1267 For convenience, any HGCommand invoked on a HGCommand scratch buffer acts
1268 as though it was invoked on the original file and splits the screen so that
1261 1269 the output appears in a new window.
1262 1270
1263 Many of the commands accept revisions as arguments. By default, most
1264 operate on the most recent revision on the current branch if no revision is
1271 Many of the commands accept revisions as arguments. By default, most
1272 operate on the most recent revision on the current branch if no revision is
1265 1273 specified (though see |HGCommandInteractive| to prompt instead).
1266 1274
1267 Each HGCommand is mapped to a key sequence starting with the <Leader>
1268 keystroke. The default mappings may be overridden by supplying different
1269 mappings before the plugin is loaded, such as in the vimrc, in the standard
1270 fashion for plugin mappings. For examples, please see
1275 Each HGCommand is mapped to a key sequence starting with the <Leader>
1276 keystroke. The default mappings may be overridden by supplying different
1277 mappings before the plugin is loaded, such as in the vimrc, in the standard
1278 fashion for plugin mappings. For examples, please see
1271 1279 |hgcommand-mappings-override|.
1272 1280
1273 The HGCommand plugin may be configured in several ways. For more details,
1281 The HGCommand plugin may be configured in several ways. For more details,
1274 1282 please see |hgcommand-customize|.
1275 1283
1276 1284 ==============================================================================
1277 1285 4. HGCommand Manual *hgcommand-manual*
1278 1286
1279 1287 4.1 HGCommand commands *hgcommand-commands*
1280 1288
1281 1289 HGCommand defines the following commands:
1282 1290
1283 1291 |:HGAdd|
1284 1292 |:HGAnnotate|
1285 1293 |:HGCommit|
1286 1294 |:HGDiff|
1287 1295 |:HGGotoOriginal|
1288 1296 |:HGLog|
1289 1297 |:HGRevert|
1290 1298 |:HGReview|
1291 1299 |:HGStatus|
1292 1300 |:HGUpdate|
1293 1301 |:HGVimDiff|
1294 1302
1295 1303 :HGAdd *:HGAdd*
1296 1304
1297 This command performs "hg add" on the current file. Please note, this does
1305 This command performs "hg add" on the current file. Please note, this does
1298 1306 not commit the newly-added file.
1299 1307
1300 1308 :HGAnnotate *:HGAnnotate*
1301 1309
1302 This command performs "hg annotate" on the current file. If an argument is
1303 given, the argument is used as a revision number to display. If not given
1304 an argument, it uses the most recent version of the file on the current
1305 branch. Additionally, if the current buffer is a HGAnnotate buffer
1310 This command performs "hg annotate" on the current file. If an argument is
1311 given, the argument is used as a revision number to display. If not given
1312 an argument, it uses the most recent version of the file on the current
1313 branch. Additionally, if the current buffer is a HGAnnotate buffer
1306 1314 already, the version number on the current line is used.
1307 1315
1308 If the |HGCommandAnnotateParent| variable is set to a non-zero value, the
1309 version previous to the one on the current line is used instead. This
1316 If the |HGCommandAnnotateParent| variable is set to a non-zero value, the
1317 version previous to the one on the current line is used instead. This
1310 1318 allows one to navigate back to examine the previous version of a line.
1311 1319
1312 The filetype of the HGCommand scratch buffer is set to 'HGAnnotate', to
1320 The filetype of the HGCommand scratch buffer is set to 'HGAnnotate', to
1313 1321 take advantage of the bundled syntax file.
1314 1322
1315 1323
1316 1324 :HGCommit[!] *:HGCommit*
1317 1325
1318 If called with arguments, this performs "hg commit" using the arguments as
1326 If called with arguments, this performs "hg commit" using the arguments as
1319 1327 the log message.
1320 1328
1321 1329 If '!' is used with no arguments, an empty log message is committed.
1322 1330
1323 If called with no arguments, this is a two-step command. The first step
1324 opens a buffer to accept a log message. When that buffer is written, it is
1325 automatically closed and the file is committed using the information from
1326 that log message. The commit can be abandoned if the log message buffer is
1331 If called with no arguments, this is a two-step command. The first step
1332 opens a buffer to accept a log message. When that buffer is written, it is
1333 automatically closed and the file is committed using the information from
1334 that log message. The commit can be abandoned if the log message buffer is
1327 1335 deleted or wiped before being written.
1328 1336
1329 Alternatively, the mapping that is used to invoke :HGCommit (by default
1330 <Leader>hgc) can be used in the log message buffer to immediately commit.
1331 This is useful if the |HGCommandCommitOnWrite| variable is set to 0 to
1337 Alternatively, the mapping that is used to invoke :HGCommit (by default
1338 <Leader>hgc) can be used in the log message buffer to immediately commit.
1339 This is useful if the |HGCommandCommitOnWrite| variable is set to 0 to
1332 1340 disable the normal commit-on-write behavior.
1333 1341
1334 1342 :HGDiff *:HGDiff*
1335 1343
1336 With no arguments, this performs "hg diff" on the current file against the
1344 With no arguments, this performs "hg diff" on the current file against the
1337 1345 current repository version.
1338 1346
1339 With one argument, "hg diff" is performed on the current file against the
1347 With one argument, "hg diff" is performed on the current file against the
1340 1348 specified revision.
1341 1349
1342 With two arguments, hg diff is performed between the specified revisions of
1350 With two arguments, hg diff is performed between the specified revisions of
1343 1351 the current file.
1344 1352
1345 This command uses the 'HGCommandDiffOpt' variable to specify diff options.
1346 If that variable does not exist, then 'wbBc' is assumed. If you wish to
1353 This command uses the 'HGCommandDiffOpt' variable to specify diff options.
1354 If that variable does not exist, then 'wbBc' is assumed. If you wish to
1347 1355 have no options, then set it to the empty string.
1348 1356
1349 1357
1350 1358 :HGGotoOriginal *:HGGotoOriginal*
1351 1359
1352 This command returns the current window to the source buffer, if the
1360 This command returns the current window to the source buffer, if the
1353 1361 current buffer is a HG command output buffer.
1354 1362
1355 1363 :HGGotoOriginal!
1356 1364
1357 Like ":HGGotoOriginal" but also executes :bufwipeout on all HG command
1365 Like ":HGGotoOriginal" but also executes :bufwipeout on all HG command
1358 1366 output buffers for the source buffer.
1359 1367
1360 1368 :HGLog *:HGLog*
1361 1369
1362 1370 Performs "hg log" on the current file.
1363 1371
1364 If an argument is given, it is passed as an argument to the "-r" option of
1372 If an argument is given, it is passed as an argument to the "-r" option of
1365 1373 "hg log".
1366 1374
1367 1375 :HGRevert *:HGRevert*
1368 1376
1369 Replaces the current file with the most recent version from the repository
1377 Replaces the current file with the most recent version from the repository
1370 1378 in order to wipe out any undesired changes.
1371
1379
1372 1380 :HGReview *:HGReview*
1373 1381
1374 Retrieves a particular version of the current file. If no argument is
1375 given, the most recent version of the file on the current branch is
1382 Retrieves a particular version of the current file. If no argument is
1383 given, the most recent version of the file on the current branch is
1376 1384 retrieved. Otherwise, the specified version is retrieved.
1377 1385
1378 1386 :HGStatus *:HGStatus*
1379 1387
1380 1388 Performs "hg status" on the current file.
1381 1389
1382 1390 :HGUpdate *:HGUpdate*
1383 1391
1384 Performs "hg update" on the current file. This intentionally does not
1385 automatically reload the current buffer, though vim should prompt the user
1392 Performs "hg update" on the current file. This intentionally does not
1393 automatically reload the current buffer, though vim should prompt the user
1386 1394 to do so if the underlying file is altered by this command.
1387 1395
1388 1396 :HGVimDiff *:HGVimDiff*
1389 1397
1390 With no arguments, this prompts the user for a revision and then uses
1391 vimdiff to display the differences between the current file and the
1392 specified revision. If no revision is specified, the most recent version
1398 With no arguments, this prompts the user for a revision and then uses
1399 vimdiff to display the differences between the current file and the
1400 specified revision. If no revision is specified, the most recent version
1393 1401 of the file on the current branch is used.
1394 1402
1395 With one argument, that argument is used as the revision as above. With
1396 two arguments, the differences between the two revisions is displayed using
1403 With one argument, that argument is used as the revision as above. With
1404 two arguments, the differences between the two revisions is displayed using
1397 1405 vimdiff.
1398 1406
1399 With either zero or one argument, the original buffer is used to perform
1400 the vimdiff. When the other buffer is closed, the original buffer will be
1407 With either zero or one argument, the original buffer is used to perform
1408 the vimdiff. When the other buffer is closed, the original buffer will be
1401 1409 returned to normal mode.
1402 1410
1403 Once vimdiff mode is started using the above methods, additional vimdiff
1404 buffers may be added by passing a single version argument to the command.
1411 Once vimdiff mode is started using the above methods, additional vimdiff
1412 buffers may be added by passing a single version argument to the command.
1405 1413 There may be up to 4 vimdiff buffers total.
1406 1414
1407 Using the 2-argument form of the command resets the vimdiff to only those 2
1408 versions. Additionally, invoking the command on a different file will
1415 Using the 2-argument form of the command resets the vimdiff to only those 2
1416 versions. Additionally, invoking the command on a different file will
1409 1417 close the previous vimdiff buffers.
1410 1418
1411 1419
1412 1420 4.2 Mappings *hgcommand-mappings*
1413 1421
1414 By default, a mapping is defined for each command. These mappings execute
1422 By default, a mapping is defined for each command. These mappings execute
1415 1423 the default (no-argument) form of each command.
1416 1424
1417 1425 <Leader>hga HGAdd
1418 1426 <Leader>hgn HGAnnotate
1419 1427 <Leader>hgc HGCommit
1420 1428 <Leader>hgd HGDiff
1421 1429 <Leader>hgg HGGotoOriginal
1422 1430 <Leader>hgG HGGotoOriginal!
1423 1431 <Leader>hgl HGLog
1424 1432 <Leader>hgr HGReview
1425 1433 <Leader>hgs HGStatus
1426 1434 <Leader>hgu HGUpdate
1427 1435 <Leader>hgv HGVimDiff
1428 1436
1429 1437 *hgcommand-mappings-override*
1430 1438
1431 The default mappings can be overriden by user-provided instead by mapping
1432 to <Plug>CommandName. This is especially useful when these mappings
1433 collide with other existing mappings (vim will warn of this during plugin
1439 The default mappings can be overriden by user-provided instead by mapping
1440 to <Plug>CommandName. This is especially useful when these mappings
1441 collide with other existing mappings (vim will warn of this during plugin
1434 1442 initialization, but will not clobber the existing mappings).
1435 1443
1436 For instance, to override the default mapping for :HGAdd to set it to
1444 For instance, to override the default mapping for :HGAdd to set it to
1437 1445 '\add', add the following to the vimrc: >
1438 1446
1439 1447 nmap \add <Plug>HGAdd
1440 1448 <
1441 1449 4.3 Automatic buffer variables *hgcommand-buffer-variables*
1442 1450
1443 Several buffer variables are defined in each HGCommand result buffer.
1444 These may be useful for additional customization in callbacks defined in
1451 Several buffer variables are defined in each HGCommand result buffer.
1452 These may be useful for additional customization in callbacks defined in
1445 1453 the event handlers (please see |hgcommand-events|).
1446 1454
1447 1455 The following variables are automatically defined:
1448 1456
1449 1457 b:hgOrigBuffNR *b:hgOrigBuffNR*
1450 1458
1451 1459 This variable is set to the buffer number of the source file.
1452 1460
1453 1461 b:hgcmd *b:hgcmd*
1454 1462
1455 This variable is set to the name of the hg command that created the result
1463 This variable is set to the name of the hg command that created the result
1456 1464 buffer.
1457 1465 ==============================================================================
1458 1466
1459 1467 5. Configuration and customization *hgcommand-customize*
1460 1468 *hgcommand-config*
1461 1469
1462 The HGCommand plugin can be configured in two ways: by setting
1463 configuration variables (see |hgcommand-options|) or by defining HGCommand
1464 event handlers (see |hgcommand-events|). Additionally, the HGCommand
1465 plugin provides several option for naming the HG result buffers (see
1466 |hgcommand-naming|) and supported a customized status line (see
1470 The HGCommand plugin can be configured in two ways: by setting
1471 configuration variables (see |hgcommand-options|) or by defining HGCommand
1472 event handlers (see |hgcommand-events|). Additionally, the HGCommand
1473 plugin provides several option for naming the HG result buffers (see
1474 |hgcommand-naming|) and supported a customized status line (see
1467 1475 |hgcommand-statusline| and |hgcommand-buffer-management|).
1468 1476
1469 1477 5.1 HGCommand configuration variables *hgcommand-options*
1470 1478
1471 Several variables affect the plugin's behavior. These variables are
1472 checked at time of execution, and may be defined at the window, buffer, or
1479 Several variables affect the plugin's behavior. These variables are
1480 checked at time of execution, and may be defined at the window, buffer, or
1473 1481 global level and are checked in that order of precedence.
1474 1482
1475 1483
1476 1484 The following variables are available:
1477 1485
1478 1486 |HGCommandAnnotateParent|
1479 1487 |HGCommandCommitOnWrite|
1480 1488 |HGCommandHGExec|
1481 1489 |HGCommandDeleteOnHide|
1482 1490 |HGCommandDiffOpt|
1483 1491 |HGCommandDiffSplit|
1484 1492 |HGCommandEdit|
1485 1493 |HGCommandEnableBufferSetup|
1486 1494 |HGCommandInteractive|
1487 1495 |HGCommandNameMarker|
1488 1496 |HGCommandNameResultBuffers|
1489 1497 |HGCommandSplit|
1490 1498
1491 1499 HGCommandAnnotateParent *HGCommandAnnotateParent*
1492 1500
1493 This variable, if set to a non-zero value, causes the zero-argument form of
1494 HGAnnotate when invoked on a HGAnnotate buffer to go to the version
1495 previous to that displayed on the current line. If not set, it defaults to
1501 This variable, if set to a non-zero value, causes the zero-argument form of
1502 HGAnnotate when invoked on a HGAnnotate buffer to go to the version
1503 previous to that displayed on the current line. If not set, it defaults to
1496 1504 0.
1497 1505
1498 1506 HGCommandCommitOnWrite *HGCommandCommitOnWrite*
1499 1507
1500 This variable, if set to a non-zero value, causes the pending hg commit to
1501 take place immediately as soon as the log message buffer is written. If
1502 set to zero, only the HGCommit mapping will cause the pending commit to
1508 This variable, if set to a non-zero value, causes the pending hg commit to
1509 take place immediately as soon as the log message buffer is written. If
1510 set to zero, only the HGCommit mapping will cause the pending commit to
1503 1511 occur. If not set, it defaults to 1.
1504 1512
1505 1513 HGCommandHGExec *HGCommandHGExec*
1506 1514
1507 This variable controls the executable used for all HG commands. If not
1515 This variable controls the executable used for all HG commands. If not
1508 1516 set, it defaults to "hg".
1509 1517
1510 1518 HGCommandDeleteOnHide *HGCommandDeleteOnHide*
1511 1519
1512 This variable, if set to a non-zero value, causes the temporary HG result
1520 This variable, if set to a non-zero value, causes the temporary HG result
1513 1521 buffers to automatically delete themselves when hidden.
1514 1522
1515 1523 HGCommandDiffOpt *HGCommandDiffOpt*
1516 1524
1517 This variable, if set, determines the options passed to the diff command of
1525 This variable, if set, determines the options passed to the diff command of
1518 1526 HG. If not set, it defaults to 'w'.
1519 1527
1520 1528 HGCommandDiffSplit *HGCommandDiffSplit*
1521 1529
1522 This variable overrides the |HGCommandSplit| variable, but only for buffers
1530 This variable overrides the |HGCommandSplit| variable, but only for buffers
1523 1531 created with |:HGVimDiff|.
1524 1532
1525 1533 HGCommandEdit *HGCommandEdit*
1526 1534
1527 This variable controls whether the original buffer is replaced ('edit') or
1535 This variable controls whether the original buffer is replaced ('edit') or
1528 1536 split ('split'). If not set, it defaults to 'edit'.
1529 1537
1530 1538 HGCommandEnableBufferSetup *HGCommandEnableBufferSetup*
1531 1539
1532 This variable, if set to a non-zero value, activates HG buffer management
1533 mode see (|hgcommand-buffer-management|). This mode means that three
1534 buffer variables, 'HGRepository', 'HGRevision' and 'HGBranch', are set if
1535 the file is HG-controlled. This is useful for displaying version
1540 This variable, if set to a non-zero value, activates HG buffer management
1541 mode see (|hgcommand-buffer-management|). This mode means that three
1542 buffer variables, 'HGRepository', 'HGRevision' and 'HGBranch', are set if
1543 the file is HG-controlled. This is useful for displaying version
1536 1544 information in the status bar.
1537 1545
1538 1546 HGCommandInteractive *HGCommandInteractive*
1539 1547
1540 This variable, if set to a non-zero value, causes appropriate commands (for
1541 the moment, only |:HGReview|) to query the user for a revision to use
1548 This variable, if set to a non-zero value, causes appropriate commands (for
1549 the moment, only |:HGReview|) to query the user for a revision to use
1542 1550 instead of the current revision if none is specified.
1543 1551
1544 1552 HGCommandNameMarker *HGCommandNameMarker*
1545 1553
1546 This variable, if set, configures the special attention-getting characters
1547 that appear on either side of the hg buffer type in the buffer name. This
1548 has no effect unless |HGCommandNameResultBuffers| is set to a true value.
1549 If not set, it defaults to '_'.
1554 This variable, if set, configures the special attention-getting characters
1555 that appear on either side of the hg buffer type in the buffer name. This
1556 has no effect unless |HGCommandNameResultBuffers| is set to a true value.
1557 If not set, it defaults to '_'.
1550 1558
1551 1559 HGCommandNameResultBuffers *HGCommandNameResultBuffers*
1552 1560
1553 This variable, if set to a true value, causes the hg result buffers to be
1554 named in the old way ('<source file name> _<hg command>_'). If not set or
1561 This variable, if set to a true value, causes the hg result buffers to be
1562 named in the old way ('<source file name> _<hg command>_'). If not set or
1555 1563 set to a false value, the result buffer is nameless.
1556 1564
1557 1565 HGCommandSplit *HGCommandSplit*
1558 1566
1559 This variable controls the orientation of the various window splits that
1560 may occur (such as with HGVimDiff, when using a HG command on a HG command
1561 buffer, or when the |HGCommandEdit| variable is set to 'split'. If set to
1562 'horizontal', the resulting windows will be on stacked on top of one
1563 another. If set to 'vertical', the resulting windows will be side-by-side.
1567 This variable controls the orientation of the various window splits that
1568 may occur (such as with HGVimDiff, when using a HG command on a HG command
1569 buffer, or when the |HGCommandEdit| variable is set to 'split'. If set to
1570 'horizontal', the resulting windows will be on stacked on top of one
1571 another. If set to 'vertical', the resulting windows will be side-by-side.
1564 1572 If not set, it defaults to 'horizontal' for all but HGVimDiff windows.
1565 1573
1566 1574 5.2 HGCommand events *hgcommand-events*
1567 1575
1568 For additional customization, HGCommand can trigger user-defined events.
1569 Event handlers are provided by defining User event autocommands (see
1570 |autocommand|, |User|) in the HGCommand group with patterns matching the
1576 For additional customization, HGCommand can trigger user-defined events.
1577 Event handlers are provided by defining User event autocommands (see
1578 |autocommand|, |User|) in the HGCommand group with patterns matching the
1571 1579 event name.
1572 1580
1573 For instance, the following could be added to the vimrc to provide a 'q'
1581 For instance, the following could be added to the vimrc to provide a 'q'
1574 1582 mapping to quit a HGCommand scratch buffer: >
1575 1583
1576 1584 augroup HGCommand
1577 1585 au HGCommand User HGBufferCreated silent! nmap <unique> <buffer> q:
1578 1586 bwipeout<cr>
1579 1587 augroup END
1580 1588 <
1581 1589
1582 1590 The following hooks are available:
1583 1591
1584 1592 HGBufferCreated This event is fired just after a hg command result
1585 buffer is created and filled with the result of a hg
1586 command. It is executed within the context of the HG
1587 command buffer. The HGCommand buffer variables may be
1588 useful for handlers of this event (please see
1593 buffer is created and filled with the result of a hg
1594 command. It is executed within the context of the HG
1595 command buffer. The HGCommand buffer variables may be
1596 useful for handlers of this event (please see
1589 1597 |hgcommand-buffer-variables|).
1590 1598
1591 1599 HGBufferSetup This event is fired just after HG buffer setup occurs,
1592 1600 if enabled.
1593 1601
1594 1602 HGPluginInit This event is fired when the HGCommand plugin first
1595 1603 loads.
1596 1604
1597 1605 HGPluginFinish This event is fired just after the HGCommand plugin
1598 1606 loads.
1599 1607
1600 1608 HGVimDiffFinish This event is fired just after the HGVimDiff command
1601 executes to allow customization of, for instance,
1609 executes to allow customization of, for instance,
1602 1610 window placement and focus.
1603 1611
1604 1612 5.3 HGCommand buffer naming *hgcommand-naming*
1605 1613
1606 By default, the buffers containing the result of HG commands are nameless
1607 scratch buffers. It is intended that buffer variables of those buffers be
1608 used to customize the statusline option so that the user may fully control
1614 By default, the buffers containing the result of HG commands are nameless
1615 scratch buffers. It is intended that buffer variables of those buffers be
1616 used to customize the statusline option so that the user may fully control
1609 1617 the display of result buffers.
1610 1618
1611 If the old-style naming is desired, please enable the
1612 |HGCommandNameResultBuffers| variable. Then, each result buffer will
1613 receive a unique name that includes the source file name, the HG command,
1614 and any extra data (such as revision numbers) that were part of the
1619 If the old-style naming is desired, please enable the
1620 |HGCommandNameResultBuffers| variable. Then, each result buffer will
1621 receive a unique name that includes the source file name, the HG command,
1622 and any extra data (such as revision numbers) that were part of the
1615 1623 command.
1616 1624
1617 1625 5.4 HGCommand status line support *hgcommand-statusline*
1618 1626
1619 It is intended that the user will customize the |'statusline'| option to
1620 include HG result buffer attributes. A sample function that may be used in
1621 the |'statusline'| option is provided by the plugin, HGGetStatusLine(). In
1622 order to use that function in the status line, do something like the
1627 It is intended that the user will customize the |'statusline'| option to
1628 include HG result buffer attributes. A sample function that may be used in
1629 the |'statusline'| option is provided by the plugin, HGGetStatusLine(). In
1630 order to use that function in the status line, do something like the
1623 1631 following: >
1624 1632
1625 1633 set statusline=%<%f\ %{HGGetStatusLine()}\ %h%m%r%=%l,%c%V\ %P
1626 1634 <
1627 1635 of which %{HGGetStatusLine()} is the relevant portion.
1628 1636
1629 The sample HGGetStatusLine() function handles both HG result buffers and
1630 HG-managed files if HGCommand buffer management is enabled (please see
1637 The sample HGGetStatusLine() function handles both HG result buffers and
1638 HG-managed files if HGCommand buffer management is enabled (please see
1631 1639 |hgcommand-buffer-management|).
1632 1640
1633 1641 5.5 HGCommand buffer management *hgcommand-buffer-management*
1634 1642
1635 The HGCommand plugin can operate in buffer management mode, which means
1636 that it attempts to set two buffer variables ('HGRevision' and 'HGBranch')
1637 upon entry into a buffer. This is rather slow because it means that 'hg
1638 status' will be invoked at each entry into a buffer (during the |BufEnter|
1643 The HGCommand plugin can operate in buffer management mode, which means
1644 that it attempts to set two buffer variables ('HGRevision' and 'HGBranch')
1645 upon entry into a buffer. This is rather slow because it means that 'hg
1646 status' will be invoked at each entry into a buffer (during the |BufEnter|
1639 1647 autocommand).
1640 1648
1641 This mode is enablmed by default. In order to disable it, set the
1642 |HGCommandEnableBufferSetup| variable to a false (zero) value. Enabling
1643 this mode simply provides the buffer variables mentioned above. The user
1644 must explicitly include those in the |'statusline'| option if they are to
1649 This mode is enabled by default. In order to disable it, set the
1650 |HGCommandEnableBufferSetup| variable to a false (zero) value. Enabling
1651 this mode simply provides the buffer variables mentioned above. The user
1652 must explicitly include those in the |'statusline'| option if they are to
1645 1653 appear in the status line (but see |hgcommand-statusline| for a simple way
1646 1654 to do that).
1647 1655
1648 1656 ==============================================================================
1649 1657 9. Tips *hgcommand-tips*
1650 1658
1651 1659 9.1 Split window annotation, by Michael Anderson >
1652 1660
1653 1661 :nmap <Leader>hgN :vs<CR><C-w>h<Leader>hgn:vertical res 40<CR>
1654 1662 \ggdddd:set scb<CR>:set nowrap<CR><C-w>lgg:set scb<CR>
1655 1663 \:set nowrap<CR>
1656 1664 <
1657 1665
1658 This splits the buffer vertically, puts an annotation on the left (minus
1659 the header) with the width set to 40. An editable/normal copy is placed on
1660 the right. The two versions are scroll locked so they move as one. and
1661 wrapping is turned off so that the lines line up correctly. The advantages
1666 This splits the buffer vertically, puts an annotation on the left (minus
1667 the header) with the width set to 40. An editable/normal copy is placed on
1668 the right. The two versions are scroll locked so they move as one. and
1669 wrapping is turned off so that the lines line up correctly. The advantages
1662 1670 are...
1663 1671
1664 1672 1) You get a versioning on the right.
1665 1673 2) You can still edit your own code.
1666 1674 3) Your own code still has syntax highlighting.
1667 1675
1668 1676 ==============================================================================
1669 1677
1670 1678 8. Known bugs *hgcommand-bugs*
1671 1679
1672 1680 Please let me know if you run across any.
1673 1681
1674 HGVimDiff, when using the original (real) source buffer as one of the diff
1675 buffers, uses some hacks to try to restore the state of the original buffer
1676 when the scratch buffer containing the other version is destroyed. There
1682 HGVimDiff, when using the original (real) source buffer as one of the diff
1683 buffers, uses some hacks to try to restore the state of the original buffer
1684 when the scratch buffer containing the other version is destroyed. There
1677 1685 may still be bugs in here, depending on many configuration details.
1678 1686
1679 1687 ==============================================================================
1680 1688
1681 1689 9. TODO *hgcommand-todo*
1682 1690
1683 1691 Integrate symlink tracking once HG will support them.
1684 1692 ==============================================================================
1685 1693 === END_DOC
1686 1694 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1687 1695 " v im:tw=78:ts=8:ft=help:norl:
1688 1696 " vim600: set foldmethod=marker tabstop=8 shiftwidth=2 softtabstop=2 smartindent smarttab :
1689 "fileencoding=iso-8859-15
1697 "fileencoding=iso-8859-15
@@ -1,102 +1,102
1 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2 2 <html>
3 3 <head>
4 4 <title>Mercurial for Windows</title>
5 5 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
6 6 <style type="text/css">
7 7 <!--
8 8 .indented
9 9 {
10 10 padding-left: 10pt;
11 11 }
12 12 -->
13 13 </style>
14 14 </head>
15 15
16 16 <body>
17 <h1>Mercurial version 0.9 for Windows</h1>
17 <h1>Mercurial version 0.9.1 for Windows</h1>
18 18
19 19 <p>Welcome to Mercurial for Windows!</p>
20 20
21 21 <p>Mercurial is a command-line application. You must run it from
22 22 the Windows command prompt (or if you're hard core, a <a
23 23 href="http://www.mingw.org/">MinGW</a> shell).</p>
24 24
25 25 <p><div class="indented"><i>Note: the standard <a
26 26 href="http://www.mingw.org/">MinGW</a> msys startup script uses
27 27 rxvt which has problems setting up standard input and output.
28 28 Running bash directly works correctly.</i></div>
29 29
30 30 <p>For documentation, please visit the <a
31 31 href="http://www.selenic.com/mercurial">Mercurial web site</a>.</p>
32 32
33 33 <p>By default, Mercurial installs to <tt>C:\Mercurial</tt>. The
34 34 Mercurial command is called <tt>hg.exe</tt>.</p>
35 35
36 36 <h1>Testing Mercurial after you've installed it</h1>
37 37
38 38 <p>The easiest way to check that Mercurial is installed properly is to
39 39 just type the following at the command prompt:</p>
40 40
41 41 <pre>
42 42 hg
43 43 </pre>
44 44
45 45 <p>This command should print a useful help message. If it does,
46 46 other Mercurial commands should work fine for you.</p>
47 47
48 48 <h1>Configuration notes</h1>
49 49 <p>The default editor for commit messages is 'vi'. You can set the EDITOR
50 50 (or HGEDITOR) environment variable to specify your preference or set it in
51 51 mercurial.ini:</p>
52 52 <pre>
53 53 [ui]
54 54 editor = whatever
55 55 </pre>
56 56
57 57
58 58 <h1>Reporting problems</h1>
59 59
60 60 <p>Before you report any problems, please consult the <a
61 61 href="http://www.selenic.com/mercurial">Mercurial web site</a> and
62 62 see if your question is already in our list of <a
63 63 href="http://www.selenic.com/mercurial/wiki/index.cgi/FAQ">Frequently
64 64 Answered Questions</a> (the "FAQ").
65 65
66 66 <p>If you cannot find an answer to your question, please feel
67 67 free to send mail to the Mercurial mailing list, at <a
68 68 href="mailto:mercurial@selenic.com">mercurial@selenic.com</a>.
69 69 <b>Remember</b>, the more useful information you include in your
70 70 report, the easier it will be for us to help you!</p>
71 71
72 72 <p>If you are IRC-savvy, that's usually the fastest way to get
73 73 help. Go to <tt>#mercurial</tt> on
74 74 <tt>irc.freenode.net</tt>.</p>
75 75
76 76 <h1>Author and copyright information</h1>
77 77
78 78 <p>Mercurial was written by <a href="http://www.selenic.com">Matt
79 79 Mackall</a>, and is maintained by Matt and a team of
80 80 volunteers.</p>
81 81
82 82 <p>The Windows installer was written by <a
83 83 href="http://www.serpentine.com/blog">Bryan
84 84 O'Sullivan</a>.</p>
85 85
86 86 <p>Mercurial is Copyright 2005, 2006 Matt Mackall and others. See the
87 87 <tt>Contributors.txt</tt> file for a list of contributors.</p>
88 88
89 89 <p>Mercurial is free software; you can redistribute it and/or
90 90 modify it under the terms of the <a
91 91 href="http://www.gnu.org/copyleft/gpl.html">GNU General Public
92 92 License</a> as published by the Free Software Foundation; either
93 93 version 2 of the License, or (at your option) any later
94 94 version.</p>
95 95
96 96 <p>Mercurial is distributed in the hope that it will be useful,
97 97 but <b>without any warranty</b>; without even the implied
98 98 warranty of <b>merchantability</b> or <b>fitness for a
99 99 particular purpose</b>. See the GNU General Public License for
100 100 more details.</p>
101 101 </body>
102 102 </html>
@@ -1,64 +1,64
1 1 ; Script generated by the Inno Setup Script Wizard.
2 2 ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
3 3
4 4 [Setup]
5 5 AppCopyright=Copyright 2005, 2006 Matt Mackall and others
6 6 AppName=Mercurial
7 AppVerName=Mercurial version 0.9
7 AppVerName=Mercurial version 0.9.1
8 8 InfoAfterFile=contrib/win32/postinstall.txt
9 9 LicenseFile=COPYING
10 10 ShowLanguageDialog=yes
11 11 AppPublisher=Matt Mackall and others
12 12 AppPublisherURL=http://www.selenic.com/mercurial
13 13 AppSupportURL=http://www.selenic.com/mercurial
14 14 AppUpdatesURL=http://www.selenic.com/mercurial
15 15 AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3}
16 16 AppContact=mercurial@selenic.com
17 OutputBaseFilename=Mercurial-0.9
17 OutputBaseFilename=Mercurial-0.9.1
18 18 DefaultDirName={sd}\Mercurial
19 19 SourceDir=C:\hg\hg-release
20 VersionInfoVersion=0.9
20 VersionInfoVersion=0.9.1
21 21 VersionInfoDescription=Mercurial distributed SCM
22 22 VersionInfoCopyright=Copyright 2005, 2006 Matt Mackall and others
23 23 VersionInfoCompany=Matt Mackall and others
24 24 InternalCompressLevel=max
25 25 SolidCompression=true
26 26 SetupIconFile=contrib\favicon.ico
27 27 AllowNoIcons=true
28 28 DefaultGroupName=Mercurial
29 29
30 30 [Files]
31 31 Source: ..\..\msys\1.0\bin\patch.exe; DestDir: {app}
32 32 Source: contrib\mercurial.el; DestDir: {app}/Contrib
33 33 Source: contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme
34 34 Source: contrib\win32\mercurial.ini; DestDir: {app}; DestName: Mercurial.ini; Flags: confirmoverwrite
35 35 Source: contrib\win32\postinstall.txt; DestDir: {app}; DestName: ReleaseNotes.txt
36 36 Source: dist\hg.exe; DestDir: {app}
37 37 Source: dist\library.zip; DestDir: {app}
38 38 Source: dist\mfc71.dll; DestDir: {sys}; Flags: sharedfile uninsnosharedfileprompt
39 39 Source: dist\msvcr71.dll; DestDir: {sys}; Flags: sharedfile uninsnosharedfileprompt
40 40 Source: dist\w9xpopen.exe; DestDir: {app}
41 41 Source: dist\add_path.exe; DestDir: {app}
42 42 Source: doc\*.txt; DestDir: {app}\Docs
43 43 Source: templates\*.*; DestDir: {app}\Templates; Flags: recursesubdirs createallsubdirs
44 44 Source: CONTRIBUTORS; DestDir: {app}; DestName: Contributors.txt
45 45 Source: COPYING; DestDir: {app}; DestName: Copying.txt
46 46 Source: comparison.txt; DestDir: {app}\Docs; DestName: Comparison.txt
47 47 Source: notes.txt; DestDir: {app}\Docs; DestName: DesignNotes.txt
48 48
49 49 [INI]
50 50 Filename: {app}\Mercurial.url; Section: InternetShortcut; Key: URL; String: http://www.selenic.com/mercurial/
51 51
52 52 [UninstallDelete]
53 53 Type: files; Name: {app}\Mercurial.url
54 54
55 55 [Icons]
56 56 Name: {group}\Uninstall Mercurial; Filename: {uninstallexe}
57 57 Name: {group}\Mercurial Command Reference; Filename: {app}\Docs\hg.1.txt
58 58 Name: {group}\Mercurial Web Site; Filename: {app}\Mercurial.url
59 59
60 60 [Run]
61 61 Filename: "{app}\add_path.exe"; Parameters: "{app}"; Flags: postinstall; Description: "Add the installation path to the search path"
62 62
63 63 [UninstallRun]
64 64 Filename: "{app}\add_path.exe"; Parameters: "/del {app}"
@@ -1,112 +1,168
1 1 Welcome to Mercurial for Windows!
2 2 ---------------------------------
3 3
4 4 For configuration and usage directions, please read the ReadMe.html
5 5 file that comes with this package.
6 6
7 7 Release Notes
8 8 -------------
9 9
10 2006-07-24 v0.9.1
11
12 Major changes between Mercurial 0.9 and 0.9.1:
13
14 New features:
15 - You can now configure your 'hgweb' server to let remote users
16 'push' changes over http.
17 - You can now 'import' a patch in a mail message by saving the mail
18 message, and importing it. This works for patches sent either
19 inline or as attachments.
20 - The 'diff' command now accepts '-rA:B' syntax as a synonym for
21 '-r A -r B', and adds '-b' and '-B' options.
22
23 New contributions and extensions:
24 - The 'acl' extension lets you lock down parts of a repository
25 against incoming changes
26 - The 'extdiff' extension lets you run your favourite graphical
27 change viewer
28 - Comprehensive integration with the 'vim' editor
29 - A restricted shell for 'ssh'-hosted repositories
30 - An importer for 'darcs' repositories
31
32 New hooks added:
33 - 'preupdate' is run before an update or merge in the working
34 directory.
35 - 'update' is run after an update or merge in the working
36 directory.
37
38 Behaviour changes:
39 - NOTE: Mercurial as installed by the Windows binary
40 installer no longer performs automatic line-ending conversion for
41 Unix/Linux compatibility. To re-enable this feature, edit your
42 'mercurial.ini' file after you upgrade.
43 - The Windows binary installer now automatically adds 'hg' to your
44 '%PATH%'.
45 - The 'backout' command now runs an editor by default, to let you
46 modify the commit message for a backed-out changeset.
47 - An earlier problem with parsing of tags has been fixed.
48 This makes tag parsing slower but more reliable.
49
50 Memory usage and performance improvements:
51 - The 'remove' command has been rewritten to be hundreds of times
52 faster in large repositories.
53 - It is now possible to 'clone' a repository very quickly over a
54 LAN, if the server is configured to allow it. See the new 'server'
55 section in the 'hgrc' documentation.
56
57 Other changes of note:
58 - Mercurial will now print help for an extension if you type 'hg
59 help EXT_NAME'.
60 - The usual array of bug fixes and documentation improvements.
61 - The integrated web server is now more WSGI-compliant.
62 - Work has begun to solidify Mercurial's API for use by third-party
63 packages.
64
65
10 66 2006-05-10 v0.9
11 67
12 68 * Major changes between Mercurial 0.8.1 and 0.9:
13 69
14 70 - The repository file format has been improved.
15 71 - This has resulted in an average 40% reduction in disk space usage.
16 72 - The new format (called RevlogNG) is now the default.
17 73 - Mercurial works perfectly with both the old and new repository
18 74 file formats. It can transfer changes transparently between
19 75 repositories of either format.
20 76 - To use the new repository format, simply use `hg clone --pull` to
21 77 clone an existing repository.
22 78 - Note: Versions 0.8.1 and earlier of Mercurial cannot read
23 79 RevlogNG repositories directly, but they can `clone`, `pull`
24 80 from, and `push` to servers that are serving RevlogNG
25 81 repositories.
26 82 - Memory usage has been improved by over 50% for many common operations.
27 83 - Substantial performance improvements on large repositories.
28 84 - New commands:
29 85 - 'archive' - generate a directory tree snapshot, tarball, or zip
30 86 file of a revision
31 87 - Deprecated commands:
32 88 - 'addremove' - replaced by 'add' and 'remove --after'
33 89 - 'forget' - replaced by 'revert'
34 90 - 'undo' - replaced by 'rollback'
35 91 - New extensions:
36 92 - Bugzilla integration hook
37 93 - Email notification hook
38 94 - Nested repositories are now supported. Mercurial will not recurse
39 95 into a subdirectory that contains a '.hg' directory. It is treated
40 96 as a separate repository.
41 97 - The standalone web server, 'hg serve', is now threaded, so it can
42 98 talk to multiple clients at a time.
43 99 - The web server can now display a "message of the day".
44 100 - Support added for hooks written in Python.
45 101 - Many improvements and clarifications to built-in help.
46 102
47 103
48 104 2006-04-07 v0.8.1
49 105
50 106 * Major changes from 0.8 to 0.8.1:
51 107
52 108 - new extensions:
53 109 mq (manage a queue of patches, like quilt only better)
54 110 email (send changes as series of email patches)
55 111 - new command: merge (replaces "update -m")
56 112 - improved commands: log (--limit option added), pull/push ("-r" works
57 113 on specific revisions), revert (rewritten, much better)
58 114 - comprehensive hook support
59 115 - output templating added, supporting e.g. GNU changelog style
60 116 - Windows, Mac OS X: prebuilt binary packages, better support
61 117 - many reliability, performance, and memory usage improvements
62 118
63 119
64 120 2006-01-29 v0.8
65 121
66 122 * Upgrade notes:
67 123
68 124 - diff and status command are now repo-wide by default
69 125 (use 'hg diff .' for the old behavior)
70 126 - GPG signing is now done with the gpg extension
71 127 - the --text option for commit, rawcommit, and tag has been removed
72 128 - the copy/rename --parents option has been removed
73 129
74 130 * Major changes from 0.7 to 0.8:
75 131
76 132 - faster status, diff, and commit
77 133 - reduced memory usage for push and pull
78 134 - improved extension API
79 135 - new bisect, gpg, hgk, and win32text extensions
80 136 - short URLs, binary file handling, and optional gitweb skin for hgweb
81 137 - numerous new command options including log --keyword and pull --rev
82 138 - improved hooks and file filtering
83 139
84 140
85 141 2005-09-21 v0.7 with modifications
86 142
87 143 * New INI files have been added to control Mercurial's behaviour:
88 144
89 145 System-wide - C:\Mercurial\Mercurial.ini
90 146 Per-user - C:\Documents and Settings\USERNAME\Mercurial.ini
91 147
92 148 A default version of the system-wide INI file is installed with
93 149 Mercurial. No per-user INI file is installed, but it will be
94 150 honoured if you create one.
95 151
96 152 * Windows line endings are now handled automatically and correctly by
97 153 the update and commit commands. See the INI file for how to
98 154 customise this behaviour.
99 155
100 156 * NOTE: Much of the rest of the Mercurial code does not handle Windows
101 157 line endings properly. Accordingly, the output of the diff command,
102 158 for example, will appear huge until I fix this.
103 159
104 160 * Packaged text files now have correct Windows line endings.
105 161
106 162
107 163 2005-09-21 v0.7 with modifications
108 164
109 165 * This is the first standalone release of Mercurial for Windows.
110 166
111 167 * I believe it to be mostly functional, with one exception: there is
112 168 no support yet for DOS <-> Unix line ending conversion.
@@ -1,221 +1,221
1 1 HG(1)
2 2 =====
3 3 Matt Mackall <mpm@selenic.com>
4 4
5 5 NAME
6 6 ----
7 7 hg - Mercurial source code management system
8 8
9 9 SYNOPSIS
10 10 --------
11 11 'hg' [-v -d -q -y] <command> [command options] [files]
12 12
13 13 DESCRIPTION
14 14 -----------
15 15 The hg(1) command provides a command line interface to the Mercurial system.
16 16
17 17 COMMAND ELEMENTS
18 18 ----------------
19 19
20 20 files ...::
21 21 indicates one or more filename or relative path filenames; see
22 22 "FILE NAME PATTERNS" for information on pattern matching
23 23
24 24 path::
25 25 indicates a path on the local machine
26 26
27 27 revision::
28 28 indicates a changeset which can be specified as a changeset revision
29 29 number, a tag, or a unique substring of the changeset hash value
30 30
31 31 repository path::
32 32 either the pathname of a local repository or the URI of a remote
33 33 repository. There are two available URI protocols, http:// which is
34 34 fast and the static-http:// protocol which is much slower but does not
35 35 require a special server on the web host.
36 36
37 37
38 38 include::hg.1.gendoc.txt[]
39 39
40 40 FILE NAME PATTERNS
41 41 ------------------
42 42
43 43 Mercurial accepts several notations for identifying one or more
44 44 files at a time.
45 45
46 46 By default, Mercurial treats filenames as shell-style extended
47 47 glob patterns.
48 48
49 49 Alternate pattern notations must be specified explicitly.
50 50
51 51 To use a plain path name without any pattern matching, start a
52 52 name with "path:". These path names must match completely, from
53 53 the root of the current repository.
54 54
55 55 To use an extended glob, start a name with "glob:". Globs are
56 56 rooted at the current directory; a glob such as "*.c" will match
57 57 files ending in ".c" in the current directory only.
58 58
59 59 The supported glob syntax extensions are "**" to match any string
60 60 across path separators, and "{a,b}" to mean "a or b".
61 61
62 62 To use a Perl/Python regular expression, start a name with "re:".
63 63 Regexp pattern matching is anchored at the root of the repository.
64 64
65 65 Plain examples:
66 66
67 67 path:foo/bar a name bar in a directory named foo in the root of
68 68 the repository
69 69 path:path:name a file or directory named "path:name"
70 70
71 71 Glob examples:
72 72
73 73 glob:*.c any name ending in ".c" in the current directory
74 74 *.c any name ending in ".c" in the current directory
75 75 **.c any name ending in ".c" in the current directory, or
76 76 any subdirectory
77 77 foo/*.c any name ending in ".c" in the directory foo
78 78 foo/**.c any name ending in ".c" in the directory foo, or any
79 79 subdirectory
80 80
81 81 Regexp examples:
82 82
83 83 re:.*\.c$ any name ending in ".c", anywhere in the repository
84 84
85 85
86 86 SPECIFYING SINGLE REVISIONS
87 87 ---------------------------
88 88
89 89 Mercurial accepts several notations for identifying individual
90 90 revisions.
91 91
92 92 A plain integer is treated as a revision number. Negative
93 93 integers are treated as offsets from the tip, with -1 denoting the
94 94 tip.
95 95
96 96 A 40-digit hexadecimal string is treated as a unique revision
97 97 identifier.
98 98
99 99 A hexadecimal string less than 40 characters long is treated as a
100 100 unique revision identifier, and referred to as a short-form
101 101 identifier. A short-form identifier is only valid if it is the
102 102 prefix of one full-length identifier.
103 103
104 104 Any other string is treated as a tag name, which is a symbolic
105 105 name associated with a revision identifier. Tag names may not
106 106 contain the ":" character.
107 107
108 108 The reserved name "tip" is a special tag that always identifies
109 109 the most recent revision.
110 110
111 111 SPECIFYING MULTIPLE REVISIONS
112 112 -----------------------------
113 113
114 114 When Mercurial accepts more than one revision, they may be
115 115 specified individually, or provided as a continuous range,
116 116 separated by the ":" character.
117 117
118 118 The syntax of range notation is [BEGIN]:[END], where BEGIN and END
119 119 are revision identifiers. Both BEGIN and END are optional. If
120 120 BEGIN is not specified, it defaults to revision number 0. If END
121 121 is not specified, it defaults to the tip. The range ":" thus
122 122 means "all revisions".
123 123
124 124 If BEGIN is greater than END, revisions are treated in reverse
125 125 order.
126 126
127 127 A range acts as a closed interval. This means that a range of 3:5
128 128 gives 3, 4 and 5. Similarly, a range of 4:2 gives 4, 3, and 2.
129 129
130 130 ENVIRONMENT VARIABLES
131 131 ---------------------
132 132
133 133 HGEDITOR::
134 134 This is the name of the editor to use when committing. Defaults to the
135 135 value of EDITOR.
136 136
137 137 (deprecated, use .hgrc)
138 138
139 139 HGMERGE::
140 140 An executable to use for resolving merge conflicts. The program
141 141 will be executed with three arguments: local file, remote file,
142 142 ancestor file.
143 143
144 144 The default program is "hgmerge", which is a shell script provided
145 145 by Mercurial with some sensible defaults.
146 146
147 147 (deprecated, use .hgrc)
148 148
149 149 HGRCPATH::
150 150 A list of files or directories to search for hgrc files. Item
151 151 separator is ":" on Unix, ";" on Windows. If HGRCPATH is not set,
152 152 platform default search path is used. If empty, only .hg/hgrc of
153 153 current repository is read.
154 154
155 155 For each element in path, if a directory, all entries in directory
156 156 ending with ".rc" are added to path. Else, element itself is
157 157 added to path.
158 158
159 159 HGUSER::
160 160 This is the string used for the author of a commit.
161 161
162 162 (deprecated, use .hgrc)
163 163
164 164 EMAIL::
165 165 If HGUSER is not set, this will be used as the author for a commit.
166 166
167 167 LOGNAME::
168 168 If neither HGUSER nor EMAIL is set, LOGNAME will be used (with
169 169 '@hostname' appended) as the author value for a commit.
170 170
171 171 EDITOR::
172 172 This is the name of the editor used in the hgmerge script. It will be
173 173 used for commit messages if HGEDITOR isn't set. Defaults to 'vi'.
174 174
175 175 PYTHONPATH::
176 176 This is used by Python to find imported modules and may need to be set
177 177 appropriately if Mercurial is not installed system-wide.
178 178
179 179 FILES
180 180 -----
181 181 .hgignore::
182 182 This file contains regular expressions (one per line) that describe file
183 183 names that should be ignored by hg. For details, see hgignore(5).
184 184
185 185 .hgtags::
186 186 This file contains changeset hash values and text tag names (one of each
187 187 separated by spaces) that correspond to tagged versions of the repository
188 188 contents.
189 189
190 190 /etc/mercurial/hgrc, $HOME/.hgrc, .hg/hgrc::
191 191 This file contains defaults and configuration. Values in .hg/hgrc
192 192 override those in $HOME/.hgrc, and these override settings made in the
193 193 global /etc/mercurial/hgrc configuration. See hgrc(5) for details of
194 194 the contents and format of these files.
195 195
196 196 BUGS
197 197 ----
198 198 Probably lots, please post them to the mailing list (See Resources below)
199 199 when you find them.
200 200
201 201 SEE ALSO
202 202 --------
203 203 hgignore(5), hgrc(5)
204 204
205 205 AUTHOR
206 206 ------
207 207 Written by Matt Mackall <mpm@selenic.com>
208 208
209 209 RESOURCES
210 210 ---------
211 211 http://selenic.com/mercurial[Main Web Site]
212 212
213 213 http://selenic.com/hg[Source code repository]
214 214
215 215 http://selenic.com/mailman/listinfo/mercurial[Mailing list]
216 216
217 217 COPYING
218 218 -------
219 Copyright \(C) 2005 Matt Mackall.
219 Copyright \(C) 2005, 2006 Matt Mackall.
220 220 Free use of this software is granted under the terms of the GNU General
221 221 Public License (GPL).
@@ -1,35 +1,35
1 1 HGMERGE(1)
2 2 ==========
3 3 Matt Mackall <mpm@selenic.com>
4 4 v0.1, 27 May 2005
5 5
6 6 NAME
7 7 ----
8 8 hgmerge - default wrapper to merge files in Mercurial SCM system
9 9
10 10 SYNOPSIS
11 11 --------
12 12 'hgmerge' local ancestor remote
13 13
14 14 DESCRIPTION
15 15 -----------
16 16 The hgmerge(1) command provides a graphical interface to merge files in the
17 17 Mercurial system. It is a simple wrapper around kdiff3, merge(1) and tkdiff(1),
18 18 or simply diff(1) and patch(1) depending on what is present on the system.
19 19
20 20 hgmerge(1) is used by the Mercurial SCM if the environment variable HGMERGE is
21 21 not set.
22 22
23 23 AUTHOR
24 24 ------
25 25 Written by Vincent Danjean <Vincent.Danjean@free.fr>
26 26
27 27 SEE ALSO
28 28 --------
29 29 hg(1) - the command line interface to Mercurial SCM
30 30
31 31 COPYING
32 32 -------
33 Copyright \(C) 2005 Matt Mackall.
33 Copyright \(C) 2005, 2006 Matt Mackall.
34 34 Free use of this software is granted under the terms of the GNU General
35 35 Public License (GPL).
@@ -1,464 +1,478
1 1 HGRC(5)
2 2 =======
3 3 Bryan O'Sullivan <bos@serpentine.com>
4 4
5 5 NAME
6 6 ----
7 7 hgrc - configuration files for Mercurial
8 8
9 9 SYNOPSIS
10 10 --------
11 11
12 12 The Mercurial system uses a set of configuration files to control
13 13 aspects of its behaviour.
14 14
15 15 FILES
16 16 -----
17 17
18 18 Mercurial reads configuration data from several files, if they exist.
19 19 The names of these files depend on the system on which Mercurial is
20 20 installed.
21 21
22 22 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
23 23 (Unix) <install-root>/etc/mercurial/hgrc::
24 24 Per-installation configuration files, searched for in the
25 25 directory where Mercurial is installed. For example, if installed
26 26 in /shared/tools, Mercurial will look in
27 27 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
28 28 all Mercurial commands executed by any user in any directory.
29 29
30 30 (Unix) /etc/mercurial/hgrc.d/*.rc::
31 31 (Unix) /etc/mercurial/hgrc::
32 32 (Windows) C:\Mercurial\Mercurial.ini::
33 33 Per-system configuration files, for the system on which Mercurial
34 34 is running. Options in these files apply to all Mercurial
35 35 commands executed by any user in any directory. Options in these
36 36 files override per-installation options.
37 37
38 38 (Unix) $HOME/.hgrc::
39 39 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini::
40 40 (Windows) $HOME\Mercurial.ini::
41 41 Per-user configuration file, for the user running Mercurial.
42 42 Options in this file apply to all Mercurial commands executed by
43 43 any user in any directory. Options in this file override
44 44 per-installation and per-system options.
45 45 On Windows system, one of these is chosen exclusively according
46 46 to definition of HOME environment variable.
47 47
48 48 (Unix, Windows) <repo>/.hg/hgrc::
49 49 Per-repository configuration options that only apply in a
50 50 particular repository. This file is not version-controlled, and
51 51 will not get transferred during a "clone" operation. Options in
52 52 this file override options in all other configuration files.
53 53
54 54 SYNTAX
55 55 ------
56 56
57 57 A configuration file consists of sections, led by a "[section]" header
58 58 and followed by "name: value" entries; "name=value" is also accepted.
59 59
60 60 [spam]
61 61 eggs=ham
62 62 green=
63 63 eggs
64 64
65 65 Each line contains one entry. If the lines that follow are indented,
66 66 they are treated as continuations of that entry.
67 67
68 68 Leading whitespace is removed from values. Empty lines are skipped.
69 69
70 70 The optional values can contain format strings which refer to other
71 71 values in the same section, or values in a special DEFAULT section.
72 72
73 73 Lines beginning with "#" or ";" are ignored and may be used to provide
74 74 comments.
75 75
76 76 SECTIONS
77 77 --------
78 78
79 79 This section describes the different sections that may appear in a
80 80 Mercurial "hgrc" file, the purpose of each section, its possible
81 81 keys, and their possible values.
82 82
83 83 decode/encode::
84 84 Filters for transforming files on checkout/checkin. This would
85 85 typically be used for newline processing or other
86 86 localization/canonicalization of files.
87 87
88 88 Filters consist of a filter pattern followed by a filter command.
89 89 Filter patterns are globs by default, rooted at the repository
90 90 root. For example, to match any file ending in ".txt" in the root
91 91 directory only, use the pattern "*.txt". To match any file ending
92 92 in ".c" anywhere in the repository, use the pattern "**.c".
93 93
94 94 The filter command can start with a specifier, either "pipe:" or
95 95 "tempfile:". If no specifier is given, "pipe:" is used by default.
96 96
97 97 A "pipe:" command must accept data on stdin and return the
98 98 transformed data on stdout.
99 99
100 100 Pipe example:
101 101
102 102 [encode]
103 103 # uncompress gzip files on checkin to improve delta compression
104 104 # note: not necessarily a good idea, just an example
105 105 *.gz = pipe: gunzip
106 106
107 107 [decode]
108 108 # recompress gzip files when writing them to the working dir (we
109 109 # can safely omit "pipe:", because it's the default)
110 110 *.gz = gzip
111 111
112 112 A "tempfile:" command is a template. The string INFILE is replaced
113 113 with the name of a temporary file that contains the data to be
114 114 filtered by the command. The string OUTFILE is replaced with the
115 115 name of an empty temporary file, where the filtered data must be
116 116 written by the command.
117 117
118 118 NOTE: the tempfile mechanism is recommended for Windows systems,
119 119 where the standard shell I/O redirection operators often have
120 120 strange effects. In particular, if you are doing line ending
121 121 conversion on Windows using the popular dos2unix and unix2dos
122 122 programs, you *must* use the tempfile mechanism, as using pipes will
123 123 corrupt the contents of your files.
124 124
125 125 Tempfile example:
126 126
127 127 [encode]
128 128 # convert files to unix line ending conventions on checkin
129 129 **.txt = tempfile: dos2unix -n INFILE OUTFILE
130 130
131 131 [decode]
132 132 # convert files to windows line ending conventions when writing
133 133 # them to the working dir
134 134 **.txt = tempfile: unix2dos -n INFILE OUTFILE
135 135
136 136 email::
137 137 Settings for extensions that send email messages.
138 138 from;;
139 139 Optional. Email address to use in "From" header and SMTP envelope
140 140 of outgoing messages.
141 to;;
142 Optional. Comma-separated list of recipients' email addresses.
143 cc;;
144 Optional. Comma-separated list of carbon copy recipients'
145 email addresses.
146 bcc;;
147 Optional. Comma-separated list of blind carbon copy
148 recipients' email addresses. Cannot be set interactively.
141 149 method;;
142 150 Optional. Method to use to send email messages. If value is
143 "smtp" (default), use SMTP (see section "[mail]" for
151 "smtp" (default), use SMTP (see section "[smtp]" for
144 152 configuration). Otherwise, use as name of program to run that
145 153 acts like sendmail (takes "-f" option for sender, list of
146 154 recipients on command line, message on stdin). Normally, setting
147 155 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
148 156 sendmail to send messages.
149 157
150 158 Email example:
151 159
152 160 [email]
153 161 from = Joseph User <joe.user@example.com>
154 162 method = /usr/sbin/sendmail
155 163
156 164 extensions::
157 165 Mercurial has an extension mechanism for adding new features. To
158 166 enable an extension, create an entry for it in this section.
159 167
160 168 If you know that the extension is already in Python's search path,
161 169 you can give the name of the module, followed by "=", with nothing
162 170 after the "=".
163 171
164 172 Otherwise, give a name that you choose, followed by "=", followed by
165 173 the path to the ".py" file (including the file name extension) that
166 174 defines the extension.
167 175
168 176 Example for ~/.hgrc:
169 177
170 178 [extensions]
171 179 # (the mq extension will get loaded from mercurial's path)
172 180 hgext.mq =
173 181 # (this extension will get loaded from the file specified)
174 182 myfeature = ~/.hgext/myfeature.py
175 183
176 184 hooks::
177 185 Commands or Python functions that get automatically executed by
178 186 various actions such as starting or finishing a commit. Multiple
179 187 hooks can be run for the same action by appending a suffix to the
180 188 action. Overriding a site-wide hook can be done by changing its
181 189 value or setting it to an empty string.
182 190
183 191 Example .hg/hgrc:
184 192
185 193 [hooks]
186 194 # do not use the site-wide hook
187 195 incoming =
188 196 incoming.email = /my/email/hook
189 197 incoming.autobuild = /my/build/hook
190 198
191 199 Most hooks are run with environment variables set that give added
192 200 useful information. For each hook below, the environment variables
193 201 it is passed are listed with names of the form "$HG_foo".
194 202
195 203 changegroup;;
196 204 Run after a changegroup has been added via push, pull or
197 unbundle. ID of the first new changeset is in $HG_NODE.
205 unbundle. ID of the first new changeset is in $HG_NODE. URL from
206 which changes came is in $HG_URL.
198 207 commit;;
199 208 Run after a changeset has been created in the local repository.
200 209 ID of the newly created changeset is in $HG_NODE. Parent
201 210 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
202 211 incoming;;
203 212 Run after a changeset has been pulled, pushed, or unbundled into
204 213 the local repository. The ID of the newly arrived changeset is in
205 $HG_NODE.
214 $HG_NODE. URL that was source of changes came is in $HG_URL.
206 215 outgoing;;
207 216 Run after sending changes from local repository to another. ID of
208 217 first changeset sent is in $HG_NODE. Source of operation is in
209 218 $HG_SOURCE; see "preoutgoing" hook for description.
210 219 prechangegroup;;
211 220 Run before a changegroup is added via push, pull or unbundle.
212 221 Exit status 0 allows the changegroup to proceed. Non-zero status
213 will cause the push, pull or unbundle to fail.
222 will cause the push, pull or unbundle to fail. URL from which
223 changes will come is in $HG_URL.
214 224 precommit;;
215 225 Run before starting a local commit. Exit status 0 allows the
216 226 commit to proceed. Non-zero status will cause the commit to fail.
217 227 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
218 228 preoutgoing;;
219 229 Run before computing changes to send from the local repository to
220 230 another. Non-zero status will cause failure. This lets you
221 231 prevent pull over http or ssh. Also prevents against local pull,
222 232 push (outbound) or bundle commands, but not effective, since you
223 233 can just copy files instead then. Source of operation is in
224 234 $HG_SOURCE. If "serve", operation is happening on behalf of
225 235 remote ssh or http repository. If "push", "pull" or "bundle",
226 236 operation is happening on behalf of repository on same system.
227 237 pretag;;
228 238 Run before creating a tag. Exit status 0 allows the tag to be
229 239 created. Non-zero status will cause the tag to fail. ID of
230 240 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
231 241 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
232 242 pretxnchangegroup;;
233 243 Run after a changegroup has been added via push, pull or unbundle,
234 244 but before the transaction has been committed. Changegroup is
235 245 visible to hook program. This lets you validate incoming changes
236 246 before accepting them. Passed the ID of the first new changeset
237 247 in $HG_NODE. Exit status 0 allows the transaction to commit.
238 248 Non-zero status will cause the transaction to be rolled back and
239 the push, pull or unbundle will fail.
249 the push, pull or unbundle will fail. URL that was source of
250 changes is in $HG_URL.
240 251 pretxncommit;;
241 252 Run after a changeset has been created but the transaction not yet
242 253 committed. Changeset is visible to hook program. This lets you
243 254 validate commit message and changes. Exit status 0 allows the
244 255 commit to proceed. Non-zero status will cause the transaction to
245 256 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
246 257 IDs are in $HG_PARENT1 and $HG_PARENT2.
247 258 preupdate;;
248 259 Run before updating the working directory. Exit status 0 allows
249 260 the update to proceed. Non-zero status will prevent the update.
250 261 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
251 262 of second new parent is in $HG_PARENT2.
252 263 tag;;
253 264 Run after a tag is created. ID of tagged changeset is in
254 265 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
255 266 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
256 267 update;;
257 268 Run after updating the working directory. Changeset ID of first
258 269 new parent is in $HG_PARENT1. If merge, ID of second new parent
259 270 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
260 271 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
261 272
262 273 Note: In earlier releases, the names of hook environment variables
263 274 did not have a "HG_" prefix. The old unprefixed names are no longer
264 275 provided in the environment.
265 276
266 277 The syntax for Python hooks is as follows:
267 278
268 279 hookname = python:modulename.submodule.callable
269 280
270 281 Python hooks are run within the Mercurial process. Each hook is
271 282 called with at least three keyword arguments: a ui object (keyword
272 283 "ui"), a repository object (keyword "repo"), and a "hooktype"
273 284 keyword that tells what kind of hook is used. Arguments listed as
274 285 environment variables above are passed as keyword arguments, with no
275 286 "HG_" prefix, and names in lower case.
276 287
277 288 A Python hook must return a "true" value to succeed. Returning a
278 289 "false" value or raising an exception is treated as failure of the
279 290 hook.
280 291
281 292 http_proxy::
282 293 Used to access web-based Mercurial repositories through a HTTP
283 294 proxy.
284 295 host;;
285 296 Host name and (optional) port of the proxy server, for example
286 297 "myproxy:8000".
287 298 no;;
288 299 Optional. Comma-separated list of host names that should bypass
289 300 the proxy.
290 301 passwd;;
291 302 Optional. Password to authenticate with at the proxy server.
292 303 user;;
293 304 Optional. User name to authenticate with at the proxy server.
294 305
295 306 smtp::
296 307 Configuration for extensions that need to send email messages.
297 308 host;;
298 Optional. Host name of mail server. Default: "mail".
309 Host name of mail server, e.g. "mail.example.com".
299 310 port;;
300 311 Optional. Port to connect to on mail server. Default: 25.
301 312 tls;;
302 313 Optional. Whether to connect to mail server using TLS. True or
303 314 False. Default: False.
304 315 username;;
305 316 Optional. User name to authenticate to SMTP server with.
306 317 If username is specified, password must also be specified.
307 318 Default: none.
308 319 password;;
309 320 Optional. Password to authenticate to SMTP server with.
310 321 If username is specified, password must also be specified.
311 322 Default: none.
312 323 local_hostname;;
313 324 Optional. It's the hostname that the sender can use to identify itself
314 325 to the MTA.
315 326
316 327 paths::
317 328 Assigns symbolic names to repositories. The left side is the
318 329 symbolic name, and the right gives the directory or URL that is the
319 330 location of the repository. Default paths can be declared by
320 331 setting the following entries.
321 332 default;;
322 333 Directory or URL to use when pulling if no source is specified.
323 334 Default is set to repository from which the current repository
324 335 was cloned.
325 336 default-push;;
326 337 Optional. Directory or URL to use when pushing if no destination
327 338 is specified.
328 339
329 340 server::
330 341 Controls generic server settings.
331 342 uncompressed;;
332 343 Whether to allow clients to clone a repo using the uncompressed
333 344 streaming protocol. This transfers about 40% more data than a
334 345 regular clone, but uses less memory and CPU on both server and
335 346 client. Over a LAN (100Mbps or better) or a very fast WAN, an
336 347 uncompressed streaming clone is a lot faster (~10x) than a regular
337 348 clone. Over most WAN connections (anything slower than about
338 349 6Mbps), uncompressed streaming is slower, because of the extra
339 350 data transfer overhead. Default is False.
340 351
341 352 ui::
342 353 User interface controls.
343 354 debug;;
344 355 Print debugging information. True or False. Default is False.
345 356 editor;;
346 357 The editor to use during a commit. Default is $EDITOR or "vi".
347 358 ignore;;
348 359 A file to read per-user ignore patterns from. This file should be in
349 360 the same format as a repository-wide .hgignore file. This option
350 361 supports hook syntax, so if you want to specify multiple ignore
351 362 files, you can do so by setting something like
352 363 "ignore.other = ~/.hgignore2". For details of the ignore file
353 364 format, see the hgignore(5) man page.
354 365 interactive;;
355 366 Allow to prompt the user. True or False. Default is True.
356 367 logtemplate;;
357 368 Template string for commands that print changesets.
358 369 style;;
359 370 Name of style to use for command output.
360 371 merge;;
361 372 The conflict resolution program to use during a manual merge.
362 373 Default is "hgmerge".
363 374 quiet;;
364 375 Reduce the amount of output printed. True or False. Default is False.
365 376 remotecmd;;
366 377 remote command to use for clone/push/pull operations. Default is 'hg'.
367 378 ssh;;
368 379 command to use for SSH connections. Default is 'ssh'.
369 380 timeout;;
370 381 The timeout used when a lock is held (in seconds), a negative value
371 382 means no timeout. Default is 600.
372 383 username;;
373 384 The committer of a changeset created when running "commit".
374 385 Typically a person's name and email address, e.g. "Fred Widget
375 386 <fred@example.com>". Default is $EMAIL or username@hostname, unless
376 387 username is set to an empty string, which enforces specifying the
377 388 username manually.
378 389 verbose;;
379 390 Increase the amount of output printed. True or False. Default is False.
380 391
381 392
382 393 web::
383 394 Web interface configuration.
384 395 accesslog;;
385 396 Where to output the access log. Default is stdout.
386 397 address;;
387 398 Interface address to bind to. Default is all.
388 399 allow_archive;;
389 400 List of archive format (bz2, gz, zip) allowed for downloading.
390 401 Default is empty.
391 402 allowbz2;;
392 403 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
393 404 Default is false.
394 405 allowgz;;
395 406 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
396 407 Default is false.
397 408 allowpull;;
398 409 Whether to allow pulling from the repository. Default is true.
399 410 allow_push;;
400 411 Whether to allow pushing to the repository. If empty or not set,
401 412 push is not allowed. If the special value "*", any remote user
402 413 can push, including unauthenticated users. Otherwise, the remote
403 414 user must have been authenticated, and the authenticated user name
404 415 must be present in this list (separated by whitespace or ",").
405 416 The contents of the allow_push list are examined after the
406 417 deny_push list.
407 418 allowzip;;
408 419 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
409 420 Default is false. This feature creates temporary files.
410 421 baseurl;;
411 422 Base URL to use when publishing URLs in other locations, so
412 423 third-party tools like email notification hooks can construct URLs.
413 424 Example: "http://hgserver/repos/"
414 425 contact;;
415 426 Name or email address of the person in charge of the repository.
416 427 Default is "unknown".
417 428 deny_push;;
418 429 Whether to deny pushing to the repository. If empty or not set,
419 430 push is not denied. If the special value "*", all remote users
420 431 are denied push. Otherwise, unauthenticated users are all denied,
421 432 and any authenticated user name present in this list (separated by
422 433 whitespace or ",") is also denied. The contents of the deny_push
423 434 list are examined before the allow_push list.
424 435 description;;
425 436 Textual description of the repository's purpose or contents.
426 437 Default is "unknown".
427 438 errorlog;;
428 439 Where to output the error log. Default is stderr.
429 440 ipv6;;
430 441 Whether to use IPv6. Default is false.
431 442 name;;
432 443 Repository name to use in the web interface. Default is current
433 444 working directory.
434 445 maxchanges;;
435 446 Maximum number of changes to list on the changelog. Default is 10.
436 447 maxfiles;;
437 448 Maximum number of files to list per changeset. Default is 10.
438 449 port;;
439 450 Port to listen on. Default is 8000.
440 451 push_ssl;;
441 452 Whether to require that inbound pushes be transported over SSL to
442 453 prevent password sniffing. Default is true.
454 stripes;;
455 How many lines a "zebra stripe" should span in multiline output.
456 Default is 1; set to 0 to disable.
443 457 style;;
444 458 Which template map style to use.
445 459 templates;;
446 460 Where to find the HTML templates. Default is install path.
447 461
448 462
449 463 AUTHOR
450 464 ------
451 465 Bryan O'Sullivan <bos@serpentine.com>.
452 466
453 467 Mercurial was written by Matt Mackall <mpm@selenic.com>.
454 468
455 469 SEE ALSO
456 470 --------
457 471 hg(1), hgignore(5)
458 472
459 473 COPYING
460 474 -------
461 475 This manual page is copyright 2005 Bryan O'Sullivan.
462 476 Mercurial is copyright 2005, 2006 Matt Mackall.
463 477 Free use of this software is granted under the terms of the GNU General
464 478 Public License (GPL).
@@ -1,867 +1,867
1 1 HG(1)
2 2 =====
3 3 Matt Mackall <mpm@selenic.com>
4 4
5 5 名前
6 6 --
7 7 hg - Mercurial ソースコード管理システム
8 8
9 9 書式
10 10 --
11 11 'hg' [-v -d -q -y] <command> [command options] [files]
12 12
13 13 説明
14 14 --
15 15 hg(1) コマンドは Mercurial システムへのコマンドラインインターフェ
16 16 イスを提供します。
17 17
18 18 オプション
19 19 ----
20 20
21 21 -R, --repository::
22 22 リポジトリのルートディレクトリを指定します。
23 23
24 24 --cwd::
25 25 作業ディレクトリを変更します。
26 26
27 27 -y, --noninteractive::
28 28 プロンプトを出さずに、要求された答えが全て 'yes' であると仮定
29 29 します。
30 30
31 31 -q, --quiet::
32 32 出力を抑制します。
33 33
34 34 -v, --verbose::
35 35 さらなる出力を可能にします。
36 36
37 37 7--debug::
38 38 デバッグ出力を可能にします。
39 39
40 40 --traceback::
41 41 例外時にトレースバックを表示します。
42 42
43 43 --time::
44 44 コマンドにどのくらい時間がかかるかを表示します。
45 45
46 46 --profile::
47 47 コマンドを実行したときのプロファイルを表示します。
48 48
49 49 --version::
50 50 バージョン情報を表示して終了します。
51 51
52 52 -h, --help::
53 53 ヘルプを表示して終了します。
54 54
55 55 コマンドの要素
56 56 -------
57 57
58 58 files ...::
59 59 1つ以上のファイル名か相対的なパスを表します; パターンマッチン
60 60 グの情報は「ファイル名のパターン」を参照してください。
61 61
62 62 path::
63 63 ローカルマシン上のパスを表します
64 64
65 65 revision::
66 66 チェンジセットのリビジョンナンバー, タグ, チェンジセットのハッ
67 67 シュ値のユニークな部分文字列により指定できるチェンジセットを表
68 68 します
69 69
70 70 repository path::
71 71 ローカルのリポジトリのパス名かリモートのリポジトリの URI を表
72 72 します。URI のプロトコルとしては現在 2 つが利用可能です。
73 73 http:// は高速で、static-http:// は遅いですがウェブのホストに特別
74 74 なサーバを必要としません。
75 75
76 76 コマンド
77 77 ----
78 78
79 79 add [options] [files ...]::
80 80 ファイルをバージョン管理下に置きリポジトリに追加することを予定
81 81 します。
82 82
83 83 ファイルは次にコミット時にリポジトリに追加されます。
84 84
85 85 ファイル名が与えられなければ、現在のディレクトリとサブディレク
86 86 トリの全てのファイルを追加します。
87 87
88 88 addremove [options] [files ...]::
89 89 新しいファイルを全て追加し無くなったファイルを全てリポジトリか
90 90 ら取り除きます。
91 91
92 92 新しいファイルは .hgignore 中のパターンにマッチした場合無視さ
93 93 れます。add のようにこの変更は次のコミット時に効果を持ちます。
94 94
95 95 annotate [-r <rev> -u -n -c] [files ...]::
96 96 ファイル中の変更を列挙し、各行の原因であるリビジョン id を表示
97 97 します。
98 98
99 99 このコマンドある変更が生じた際に誰がその変更をしたかを発見する
100 100 のに役に立ちます。
101 101
102 102 -a オプションが無いと、annotate はバイナリとして検出されたファ
103 103 イルを避けるようになります。-a があると、annotate はとくかく注
104 104 釈を生成し、おそらく望ましくない結果になるでしょう。
105 105
106 106 オプション:
107 107 -a, --text 全てのファイルをテキストとして扱います
108 108 -I, --include <pat> 与えられたパターンにマッチした名前を含め
109 109 ます
110 110 -X, --exclude <pat> 与えられたパターンにマッチした名前を除外
111 111 します
112 112 -r, --revision <rev> 指定されたリビジョンの注釈を生成します
113 113 -u, --user 著者を列挙します
114 114 -c, --changeset チェンジセットを列挙します
115 115 -n, --number リビジョンナンバーを列挙します
116 116 (デフォルト)
117 117
118 118 bundle <file> <other>::
119 119 (実験的)
120 120
121 121 他のリポジトリには見付からなかった全てのチェンジセットを集めた、
122 122 圧縮済みチェンジグループファイルを生成します。
123 123
124 124 このファイルは従来の方法で転送することができ、他のリポジトリに
125 125 unbundle コマンドで適用できます。これは push と pull が使えな
126 126 いか、リポジトリ全体をエクスポートしてしまうことが望ましくない
127 127 ときに便利です。標準のファイル拡張子は ".hg" です。
128 128
129 129 import/export と違って、これはパーミッション、名前変更のデータ、
130 130 リビジョンの履歴を含めたチェンジセットの内容全てを保存します。
131 131
132 132 cat [options] <file ...>::
133 133 指定されたファイルを与えられたリビジョンの内容で表示します。リ
134 134 ビジョンが指定されなかった場合は tip が使われます。
135 135
136 136 出力はファイルに対しても可能です。その場合、ファイル名はフォー
137 137 マット文字列により指定されます。フォーマット規則は export コマ
138 138 ンドと同じですが、さらに次のものが追加されます。
139 139
140 140 %s 出力されるファイルのベース名
141 141 %d 出力されるファイルのディレクトリ名か、リポジトリのルート
142 142 にいる場合は "."
143 143 %p 出力されるファイルのルートからの相対パス
144 144
145 145 オプション:
146 146 -I, --include <pat> 与えられたパターンにマッチした名前
147 147 を含めます
148 148 -X, --exclude <pat> 与えられたパターンにマッチした名前
149 149 を除外します
150 150 -o, --output <filespec> 整形された名前でファイルに出力しま
151 151
152 152 -r, --rev <rev> 与えられたリビジョンを表示します
153 153
154 154 clone [-U] <source> [dest]::
155 155 既存のリポジトリのコピーを新しいディレクトリに作成します
156 156
157 157 コピー先のディレクトリ名が指定されなければ、デフォルトでソース
158 158 のベース名を使用します。
159 159
160 160 今後の pull に使えるように、コピー元が新しいリポジトリの
161 161 .hg/hgrc に追加されます。
162 162
163 163 効率のために、コピー元とコピー先が同じファイルシステム上にある
164 164 場合はハードリンクが使われます。
165 165
166 166 オプションン:
167 167 -U, --noupdate 新しい作業ディレクトリで update を行いません
168 168 -e, --ssh 使用される ssh コマンドを指定します
169 169 --remotecmd リモート側で実行する hg コマンドを指定します
170 170
171 171 commit [options] [files...]::
172 172 指定されたファイルの変更をリポジトリにコミットします。
173 173
174 174 もしファイルのリストが省略されたら、リポジトリのルートから実行
175 175 した"hg status" で報告される全ての変更がコミットされます。
176 176
177 177 HGEDITOR や EDITOR 環境変数はコミット時のコメントを追加するエ
178 178 ディタを起動するために使われます。
179 179
180 180 オプション:
181 181
182 182 -A, --addremove コミット中に addremove を実行します
183 183 -I, --include <pat> 与えられたパターンにマッチした名前を含め
184 184 ます
185 185 -X, --exclude <pat> 与えられたパターンにマッチした名前を除外
186 186 します
187 187 -m, --message <text> <text> をコミットメッセージとして使いま
188 188
189 189 -l, --logfile <file> <file> からコミットメッセージを読み込み
190 190 ます
191 191 -d, --date <datecode> datecode をコミットした日付として記録し
192 192 ます
193 193 -u, --user <user> user をコミッタとして記録します。
194 194
195 195 別名: ci
196 196
197 197 copy <source ...> <dest>::
198 198 コピー先がコピー元のファイルのコピーを持っていると印を付けます。
199 199 もしコピー先がディレクトリなら、コピーはディレクトリ中に置かれ
200 200 ます。もしコピー先がファイルなら、コピー元は1つのみ指定可能で
201 201 す。
202 202
203 203 デフォルトでは、このコマンドはファイルがその作業ディレクトリに
204 204 あるものとしてその内容をコピーします。もし --after と一緒に呼
205 205 び出されれば、操作は記録されますが、コピーは実行されません。
206 206
207 207 このコマンドは次のコミット時に効果を持ちます。
208 208
209 209 注意: このコマンドは実験的です。リネームされたファイルを適切に
210 210 記録できますが、この情報はマージによってまだ完全には使われませ
211 211 んし、ログで完全に報告されることもありません。
212 212
213 213 オプション:
214 214 -A, --after 既に発生したコピーを記録します。
215 215 -I, --include <pat> 与えられたパターンにマッチした名前を含め
216 216 ます
217 217 -X, --exclude <pat> 与えられたパターンにマッチした名前を除外
218 218 します
219 219 -f, --force 既存の変更されたファイルに無理矢理コピー
220 220 します
221 221 -p, --parents コピー先にコピー元のパスを追加します
222 222
223 223 別名: cp
224 224
225 225 diff [-a] [-r revision] [-r revision] [files ...]::
226 226 指定されたファイルのリビジョン間の差分を表示します。
227 227
228 228 ファイル間の差分は unified diff 形式で表示されます。
229 229
230 230 2つのリビジョンが引数として指定された場合、それらのリビジョン
231 231 間の差分が表示されます。1つのリビジョンしか指定されなければ、
232 232 そのリビジョンは作業ディレクトリと比較されます。そして リビジョ
233 233 ンが指定されなければ、作業ディレクトリのファイルがその親と比較
234 234 されます。
235 235
236 236 -a オプション無しでは、diff はバイナリファイルを検出したときに
237 237 その差分を生成するのを避けます。-a オプションでは、diff はとに
238 238 かく差分を生成し、恐らく望ましくない結果をもたらすでしょう。
239 239
240 240 オプション:
241 241 -a, --text 全てのファイルをテキストとして扱います
242 242 -I, --include <pat> 与えられたパターンにマッチした名前を含め
243 243 ます
244 244 -X, --exclude <pat> 与えられたパターンにマッチした名前を除外
245 245 します
246 246
247 247 export [-o filespec] [revision] ...::
248 248 1つ以上のリビジョンのチェンジセットのヘッダと差分を出力します。
249 249
250 250 チェンジセットのヘッダに表示される情報は: 著者、チェンジセット
251 251 のハッシュ、親、コミット時のコメントです。
252 252
253 253 出力はファイルに対しても可能です。その場合、ファイル名はフォー
254 254 マット文字列により指定されます。フォーマット規則は下記の通りで
255 255 す:
256 256
257 257 %% そのままの "%" 文字
258 258 %H チェンジセットのハッシュ (40 バイトの 16 進数)
259 259 %N 生成されているパッチの番号
260 260 %R チェンジセットのリビジョンナンバー
261 261 %b エクスポートしているリポジトリのメース名
262 262 %h 短い形式のチェンジセットのハッシュ (12 バイトの 16 進数)
263 263 %n 0 で 詰められた 1 から始まる連番
264 264 %r 0 で 詰められたリビジョンナンバー
265 265
266 266 -a オプション無しでは、diff はバイナリファイルを検出したときに
267 267 その差分を生成するのを避けます。-a オプションでは、diff はとに
268 268 かく差分を生成し、恐らく望ましくない結果をもたらすでしょう。
269 269
270 270 オプション:
271 271 -a, --text 全てのファイルをテキストとして扱います
272 272 -o, --output <filespec> 整形された名前でファイルに出力します
273 273
274 274 forget [options] [files]::
275 275 次のコミット時に予定された 'hg add' を取り消します。
276 276
277 277 オプション:
278 278 -I, --include <pat> 与えられたパターンにマッチした名前を含めま
279 279 -すX, --exclude <pat> 与えられたパターンにマッチした名前を除外
280 280 -します
281 281
282 282 grep [options] pattern [files]::
283 283 正規表現によりファイルのリビジョンを検索します。
284 284
285 285 このコマンドは Unix の grep とは違う振舞いをします。これは
286 286 Python/Perl の正規表現だけを受けつけます。これは作業ディレクト
287 287 リではなくリポジトリの履歴を検索します。これは常にマッチしたも
288 288 のが現れたリビジョンナンバーを表示します。
289 289
290 290 デフォルトでは、grep はマッチしたものが見つかったファイルの最
291 291 初のリビジョンを出力します。マッチ状況の変化("-" はマッチが非
292 292 マッチに、"+" は非マッチがマッチに)を含んだ各リビジョンを表示
293 293 するには、--all フラグを使ってください。
294 294
295 295 オプション:
296 296 -0, --print0 ファイル名を NUL で終えます。
297 297 -I, --include <pat> 与えられたパターンにマッチした名前
298 298 を含めます
299 299 -X, --exclude <pat> 与えられたパターンにマッチした名前
300 300 を除外します
301 301 --all マッチした全てのリビジョンを表示し
302 302 ます
303 303 -i, --ignore-case マッチのときに英大文字と小文字を区
304 304 別しないようにします
305 305 -l, --files-with-matches マッチしたファイル名とリビジョンの
306 306 みを表示します
307 307 -n, --line-number マッチした行番号を表示します
308 308 -r, --rev <rev> 指定されたリビジョンの間で検索しま
309 309
310 310 -u, --user その変更をコミットしたユーザを表示
311 311 します
312 312
313 313 heads::
314 314 リポジトリの先頭のチェンジセットを全て表示します。
315 315
316 316 リポジトリの「先頭」とは子のチェンジセットを持っていないチェン
317 317 ジセットです。それらは大抵開発が行われる場所で、通常 update と
318 318 merge 操作の対象となるところです。
319 319
320 320 identify::
321 321 レポジトリの現在の状態の短いサマリを表示します。
322 322
323 323 このサマリはリポジトリの状態を1つまたは2つの親のハッシュ識別子
324 324 を使って識別します。親のハッシュ識別子はもし作業ディレクトリに
325 325 コミットされていない変更があれば後ろに + が付き、更にその後に
326 326 このリビジョンのタグのリストが付きます。
327 327
328 328 別名: id
329 329
330 330 import [-p <n> -b <base> -f] <patches>::
331 331 一連のパッチをインポートし、それぞれ個別にコミットします。
332 332
333 333 作業ディレクトリに未解決の変更があった場合、import は -f フラ
334 334 グが指定されてなければ中断します。
335 335
336 336 もしパッチがメールのよう(最初の行が "From " か RFC 822 ヘッダ
337 337 のよう) であれば、-f オプションが使われていない限りそれは適用
338 338 されません。インポート機構はメールのヘッダをパースもしなければ
339 339 破棄もしないので、本物のメールをインポートしないようにする「メー
340 340 ルのようなものの」健全性チェックを上書きするためだけに -f を使っ
341 341 てください。
342 342
343 343 オプション:
344 344 -p, --strip <n> patch の ディレクトリ除去オプションです。これ
345 345 は関連する patch のオプションと同じ意味を持ち
346 346 ます
347 347 -b <path> パッチを読み込むベースとなるディレクトリを指
348 348 定します
349 349 -f, --force 未解決でまだコミットされていない変更のチェッ
350 350 クを省略します
351 351
352 352 別名: patch
353 353
354 354 incoming [-p] [source]::
355 355 指定されたリポジトリか、デフォルトで pull するリポジトリ中に見
356 356 つかった新しいチェンジセットを表示します。これらは pull が要求
357 357 されたときにpull されるチェンジセットです。
358 358
359 359 現在はローカルのリポジトリのみがサポートされています。
360 360
361 361 オプション:
362 362 -p, --patch パッチを表示します
363 363
364 364 別名: in
365 365
366 366 init [dest]::
367 367 指定されたディレクトリ中に新しいリポジトリを初期化します。指定
368 368 されたディレクトリが存在しない場合は作成されます。
369 369
370 370 ディレクトリが指定されなければ、現在のディレクトリが使用されま
371 371 す。
372 372
373 373 locate [options] [files]::
374 374 Mercurial の管理下にあるファイルで名前が指定されたパターンにマッ
375 375 チしたものを全て表示します。
376 376
377 377 このコマンドは現在のディレクトリとサブディレクトリを検索します。
378 378 リポジトリ全体を検索するには、リポジトリのルートに移動してくだ
379 379 さい。
380 380
381 381 もしマッチするためのパターンが与えられなければ、このコマンドは
382 382 全てのファイルの名前を表示します。
383 383
384 384 もしこのコマンドの出力を "xargs" コマンドに送りたいなら、
385 385 "-0" オプションをこのコマンドと "xargs" コマンドの両方で使用し
386 386 てください。これは "xargs" がスペースの入ったファイル名を複数
387 387 のファイル名として扱わないようにします。
388 388
389 389 オプション:
390 390
391 391 -0, --print0 xargs と一緒に使うために、ファイル名を
392 392 NUL で終えます
393 393 -f, --fullpath ファイルシステムのルートからの完全なパ
394 394 スを表示します
395 395 -I, --include <pat> 与えられたパターンにマッチした名前を含
396 396 めます
397 397 -r, --rev <rev> rev のときのリポジトリを検索します
398 398 -X, --exclude <pat> 与えられたパターンにマッチした名前を除外
399 399 します
400 400
401 401 log [-r revision ...] [-p] [files]::
402 402 指定されたファイルかプロジェクト全体のリビジョンの履歴を表示し
403 403 ます。
404 404
405 405 デフォルトではこのコマンドは次のものを出力します: チェンジセッ
406 406 トのid とハッシュ、タグ、親、ユーザ、日付、各コミットのサマ
407 407 リ。-v スイッチは変更されたファイルやマニフェストのハッシュ、
408 408 メッセージのシグネチャといったより詳しい情報を追加します。
409 409
410 410 オプション:
411 411 -I, --include <pat> 与えられたパターンにマッチした名前を含め
412 412 ます
413 413 -X, --exclude <pat> 与えられたパターンにマッチした名前を除外
414 414 します
415 415 -r, --rev <A> 指定されたリビジョンまたは範囲を表示しま
416 416
417 417 -p, --patch パッチを表示します
418 418
419 419 別名: history
420 420
421 421 manifest [revision]::
422 422 指定されたリビジョンでバージョン管理されているファイルのリスト
423 423 を表示します。
424 424
425 425 manifest はバージョン管理されているファイルのリストです。もし
426 426 リビジョンが指定されなければ、tip が使われます。
427 427
428 428 outgoing [-p] [dest]::
429 429 指定された行き先のリポジトリかデフォルトで push するリポジトリ
430 430 中に見付からなかったチェンジセットを表示します。これらは push
431 431 が要求されたときに push されるであろうチェンジセットです。
432 432
433 433 オプション:
434 434 -p, --patch パッチを表示します
435 435
436 436 別名: out
437 437
438 438 parents::
439 439 作業ディレクトリの親リビジョンを表示します。
440 440
441 441 paths [NAME]::
442 442 シンボルのパス名である NAME の行き先を表示します。もしシンボル
443 443 名が指定されなければ、利用可能なシンボル名の行き先を表示します。
444 444
445 445 パス名は /etc/mercurial/hgrc と $HOME/.hgrc の [paths] セクショ
446 446 ンで定義されます。もしリポジトリの内部で実行された場
447 447 合、.hg/hgrc も使用されます。
448 448
449 449 pull <repository path>::
450 450 リモートのリポジトリの変更をローカルのリポジトリに pull します。
451 451
452 452 これは指定されたパスや URL にあるリポジトリの全ての変更を見つ
453 453 けて、それらをローカルのリポジトリに追加します。デフォルトでは、
454 454 これは作業ディレクトリのプロジェクトのコピーを更新しません。
455 455
456 456 有効な URL の次の形式です:
457 457
458 458 local/filesystem/path
459 459 http://[user@]host[:port][/path]
460 460 https://[user@]host[:port][/path]
461 461 ssh://[user@]host[:port][/path]
462 462
463 463 SSH は行き先のマシンのシェルアカウントと、リモートのパスにhg
464 464 のコピーが必要になります。SSH を使用すると、パスはデフォルトで
465 465 はリモートのユーザのホームディレクトリの相対パスになります; ファ
466 466 イルシステムのルートからの相対パスであることを指定するには、パ
467 467 スの最初にスラッシュを 2つ使用してください。
468 468
469 469 オプション:
470 470 -u, --update pull の後に作業ディレクトリを tip に更新します
471 471 -e, --ssh 使用する ssh コマンドを指定します
472 472 --remotecmd リモート側で使われる hg コマンドを指定します
473 473
474 474 push <destination>::
475 475 ローカルのリポジトリの変更を指定された行き先に push します。
476 476
477 477 これは pull と対称的な操作です。これは現在のリポジトリの変更を
478 478 他のリポジトリへ移すのに役立ちます。もし行き先がローカルであれ
479 479 ば、これはそのディレクトリから現在のディレクトリに対して pull
480 480 するのと同じです。
481 481
482 482 デフォルトでは、push は実行した結果リモートのヘッドの数が増え
483 483 るならば、実行を拒否します。これはたいていクライアントが push
484 484 する前に sync とmerge を忘れていることを意味します。
485 485
486 486 有効な URL は次の形式です:
487 487
488 488 local/filesystem/path
489 489 ssh://[user@]host[:port][/path]
490 490
491 491 SSH は行き先のマシンのシェルアカウントと、リモートのパスに hg
492 492 のコピーが必要になります。
493 493
494 494 オプション:
495 495
496 496 -f, --force update を強行します
497 497 -e, --ssh 使用される ssh コマンドを指定します
498 498 --remotecmd リモート側で実行される hg コマンドを指定します
499 499
500 500 rawcommit [-p -d -u -F -m -l]::
501 501 低レベルのコミットで、ヘルパースクリプト中で使用されます。
502 502
503 503 このコマンドは通常のユーザに使われることは想定していません。こ
504 504 れは主に他の SCM からインポートするときに便利です。
505 505
506 506 recover::
507 507 中断された commit や pull から復帰します。
508 508
509 509 このコマンドは中断された操作からリポジトリの状態を修整しようと
510 510 試みます。これは Mercurial がそうするよう提案したときのみ必要
511 511 でしょう。
512 512
513 513 remove [options] [files ...]::
514 514 指定されたファイルをリポジトリから削除することを予定します。
515 515
516 516 このコマンドはファイルを次のコミット時に削除することを予定しま
517 517 す。このコマンドはファイルを現在の枝から取り除くだけで、プロジェ
518 518 クトの履歴全体からは削除しません。もしファイルが作業ディレクト
519 519 リ中にまだ存在していれば、それらは作業ディレクトリから削除され
520 520 ます。
521 521
522 522 別名: rm
523 523
524 524 rename <source ...> <dest>::
525 525 コピー先をコピー元のコピーのコピーであると印をつけます; コピー
526 526 元に削除の印をつけます。もしコピー先がディレクトリであれば、コ
527 527 ピーはそのディレクトリ中に置かれます。もしコピー先がファイルな
528 528 ら、コピー元は 1 つのみ指定可能です。
529 529
530 530 デフォルトでは、このコマンドはファイルがその作業ディレクトリに
531 531 あるものとしてその内容をコピーします。もし --after と一緒に呼
532 532 び出されれば、操作は記録されますが、コピーは実行されません。
533 533
534 534 このコマンドは次のコミット時に効果を持ちます。
535 535
536 536 注意: このコマンドは実験的です。リネームされたファイルを適切に
537 537 記録できますが、この情報はマージによってまだ完全には使われませ
538 538 んし、ログで完全に報告されることもありません。
539 539
540 540 オプション:
541 541 -A, --after 既に発生したリネームを記録します
542 542 -f, --force 既存の変更されたファイルに無理矢理コピーし
543 543 ます
544 544 -p, --parents コピー先にコピー元のパスを追加します
545 545
546 546 別名: mv
547 547
548 548 revert [names ...]::
549 549 指定されたファイルやディレクトリの未コミットの変更を取り消しま
550 550 す。これは関連したファイルの内容をコミットされてない状態に戻し
551 551 ます。
552 552
553 553 もしファイルが削除されていれば、再作成されます。もしファイルの
554 554 実行モードが変更されていれば、リセットされます。
555 555
556 556 ディレクトリが指定された場合、そのディレクトリ中のすべてのファ
557 557 イルとサブディレクトリが元に戻されます。
558 558
559 559 もし引数が指定されなければ、現在のディレクトリ中の全てのファイ
560 560 ルとサブディレクトリが元の戻されます。
561 561
562 562 オプション:
563 563 -r, --rev <rev> 元に戻す先のリビジョンを指定します
564 564 -n, --nonrecursive サブディレクトリを再帰的に辿らないように
565 565 します
566 566
567 567 root::
568 568 現在のリポジトリのルートディレクトリを表示します。
569 569
570 570 serve [options]::
571 571 ローカルの HTTP リポジトリと pull サーバを起動します。
572 572
573 573 デフォルトでは、サーバはアクセスログを標準出力に、エラーログを
574 574 標準エラー出力に出力します。ファイルにログを取るには "-A" と
575 575 "-E" オプションを使ってください。
576 576
577 577 オプション:
578 578 -A, --accesslog <file> アクセスログが出力されるファイルの名前
579 579 を指定します
580 580 -E, --errorlog <file> エラーログが出力されるファイルの名前を
581 581 指定します
582 582 -a, --address <addr> 使用するアドレスを指定します
583 583 -p, --port <n> 使用するポートを指定します
584 584 (デフォルト: 8000)
585 585 -n, --name <name> ウェブページで表示する名前を指定します
586 586 (デフォルト: working dir)
587 587 -t, --templatedir <path> 使用するウェブの雛型を指定します
588 588 -6, --ipv6 IPv4 に加えて IPv6 も使用します
589 589
590 590 status [options] [files]::
591 591 作業ディレクトリ中の変更されたファイルを表示します。名前が指定
592 592 されなければ、全てのファイルが表示されます。名前が指定されれば、
593 593 指定された名前にマッチしたファイルのみが表示されます。
594 594
595 595 ファイルの状態を表示するのに使われる記号:
596 596
597 597 M = 変更されました
598 598 A = 追加されました
599 599 R = 削除されました
600 600 ? = バージョン管理下にありません
601 601
602 602 オプション:
603 603
604 604 -m, --modified 変更されたファイルのみを表示します
605 605 -a, --added 追加されたファイルのみを表示します
606 606 -r, --removed 削除されたファイルのみを表示します
607 607 -u, --unknown 不明な(バージョン管理下にない)ファイルのみ
608 608 を表示します
609 609 -n, --no-status 状態を示す接頭辞を隠します
610 610 -0, --print0 xargs と一緒に使うために、ファイル名を NUL
611 611 で終えます
612 612 -I, --include <pat> 与えられたパターンにマッチした名前を含めま
613 613
614 614 -X, --exclude <pat> 与えられたパターンにマッチした名前を除外し
615 615 ます
616 616
617 617 tag [-l -m <text> -d <datecode> -u <user>] <name> [revision]::
618 618 特定のリビジョンに <name> を使って名前を付けます。
619 619
620 620 タグはリポジトリの特定のリビジョンに名前を付けるために使われ、
621 621 そして異なるリビジョンを比較したり、重要な以前のバージョンに戻っ
622 622 たり、リリース等の分岐点に印をつけたりするのに便利です。
623 623
624 624 もしバージョンが指定されなければ、tip が使われます。
625 625
626 626 バージョン管理、配布、タグのマージを楽にするために、それらは
627 627 ".hgtags" という名前のファイルに格納され、他のプロジェクトのファ
628 628 イルと同様に扱ったり、必要であれば手で編集できます。
629 629
630 630 オプション:
631 631 -l, --local タグをローカルにします
632 632 -m, --message <text> タグをコミットしたときのログのエントリの
633 633 メッセージを指定します
634 634 -d, --date <datecode> コミットの日付を指定します
635 635 -u, --user <user> コミットするユーザを指定します
636 636
637 637 注意: ローカルのタグはバージョン管理や配布されることはなく、ま
638 638 た. hg/localtags ファイルに格納されます。もし同じ名前のローカ
639 639 ルのタグと公開されたタグがあれば、ローカルのタグが使われます。
640 640
641 641 tags::
642 642 リポジトリのタグを列挙します。
643 643
644 644 これは通常のタグとローカルのタグを両方列挙します。
645 645
646 646 tip::
647 647 tip のリビジョンを表示します。
648 648
649 649 unbundle <file>::
650 650 (実験的)
651 651
652 652 bundle コマンドで生成された、圧縮済みチェンジグループファイル
653 653 を適用します。
654 654
655 655 undo::
656 656 最後の commit や pull の処理を取り消します。
657 657
658 658 リポジトリの最後の pull や commit 処理を巻戻し、プロジェクトを
659 659 それより前の状態に戻します。
660 660
661 661 このコマンドは注意して使ってください。まだ 1回の undo だけで、
662 662 redo はありません。
663 663
664 664 このコマンドは公開したリポジトリで使われることは想定していませ
665 665 ん。いったん他のユーザから pull で変更が見えるようになれば、ロー
666 666 カルでそれを取り消しても意味がありません。
667 667
668 668 update [-m -C] [revision]::
669 669 作業ディレクトリを指定されたリビジョンに更新します。
670 670
671 671 デフォルトでは、更新によりローカルの変更をマージしたり破棄した
672 672 りすることが必要となるとき、update はそれを拒否します。
673 673
674 674 -m オプションで、マージが実行されます。
675 675
676 676 -C オプションで、ローカルの変更が失われます。
677 677
678 678 オプション:
679 679 -m, --merge 枝のマージを許可します
680 680 -C, --clean ローカルで変更されたファイルを上書きします
681 681
682 682 別名: up checkout co
683 683
684 684 verify::
685 685 現在のリポジトリの整合性を検証します。
686 686
687 687 これはリポジトリの整合性を全面的にチェックし、チェンジログの各
688 688 エントリ、manifest, 管理下のファイルのハッシュとチェックサムを
689 689 検証し、またクロスリンクとインデクスの整合性も検証します。
690 690
691 691 ファイル名とパターン
692 692 ---------
693 693
694 694 Mercurial では同時に複数のファイルを識別するのに複数の記法が使
695 695 えます。
696 696
697 697 デフォルトでは、Mercurial はファイル名をシェルのスタイルの拡張
698 698 glob パターンとして扱います。
699 699
700 700 別のパターン表記は明示的に指定する必要があります。
701 701
702 702 パターンマッチングなしの単純なパス名を使うには、パス名を
703 703 "path:" で始めてください。これらのパス名は、現在のリポジトリの
704 704 ルートから完全にマッチしている必要があります。
705 705
706 706 拡張 glob を使うには、名前を "glob:" で始めてください。glob は
707 707 現在のディレクトリのみに適用されます: "*.c" といった glob は現
708 708 在のディレクトリ中の ".c" で終わるファイルのみにマッチします。
709 709
710 710 サポートされている glob 文法の拡張はパスの分離記号を越えて全て
711 711 の文字列にマッチする "**" と、"a または b" を意味する "{a,b}"
712 712 です。
713 713
714 714 Perl/Python の正規表現を使うには、名前を "re:" で始めてくださ
715 715 い。正規表現によるマッチはリポジトリのルートの固定されています。
716 716
717 717 単純な例:
718 718
719 719 path:foo/bar リポジトリのルートにある foo というディレクトリ
720 720 の bar という名前
721 721 path:path:name "path:name" という名前のファイルまたはディレク
722 722 トリ
723 723
724 724 Glob の例:
725 725
726 726 glob:*.c 現在のディレクトリ中の ".c" で終わる全ての名前
727 727 *.c 現在のディレクトリ中の ".c" で終わる全ての名前
728 728 **.c 現在のディレクトリと全てのサブディレクトリ中の
729 729 ".c" で終わる全ての名前
730 730 foo/*.c ディレクトリ foo 中の ".c" で終わる全ての名前
731 731 foo/**.c ディレクトリ foo とその全てのサブディレクトリ中
732 732 の ".c" で終わる全ての名前
733 733
734 734 正規表現の例:
735 735
736 736 re:.*\.c$ リポジトリ全体の中の ".c" で終わる全ての名前
737 737
738 738
739 739 単一のリビジョンの指定法
740 740 -----------
741 741
742 742 Mercurial では個々のリビジョンを識別するのに複数の記法が使えま
743 743 す。
744 744
745 745 単純な整数はリビジョンナンバーとして扱われます。負の整数はtip
746 746 からのオフセットとして扱われ、-1 が tip を表します。
747 747
748 748 40 桁の 16 進数の文字列はユニークなリビジョン識別子として扱わ
749 749 れます。
750 750
751 751 40 桁より少ない 16 進数の文字列はユニークなリビジョン識別子と
752 752 して扱われ、短い形式の識別子と呼ばれます。短い形式の識別子はそ
753 753 れが完全な長さの識別子の接頭語であるときだけ有効です。
754 754
755 755 他の文字列は全てタグ名として扱われます。タグはあるリビジョン識
756 756 別子に関連付けられたシンボル名です。タグ名は ":" 文字を含んで
757 757 はいけません。
758 758
759 759 リビジョン名 "tip" は特別なタグで、常に一番最新のリビジョンを
760 760 指します。
761 761
762 762 複数のリビジョンの指定法
763 763 -----------
764 764
765 765 Mercurial が 1つより多くのリビジョンを受けいれるとき、それらは
766 766 別々に指定されるか、連続した範囲として ":" 文字で区切って与え
767 767 られるかもれません。
768 768
769 769 範囲表記の構文は [BEGIN]:[END] で BEGIN と END はリビジョンの
770 770 識別子です。BEGIN も END も両方とも任意です。もし BEGIN が指定
771 771 されなければ、デフォルトでリビジョンナンバー 0 になります。も
772 772 し END が指定されなければ、デフォルトで tip になります。従って
773 773 範囲 ":" は "全てのリビジョン" を意味します。
774 774
775 775 もし BEGIN が END より大きければ、リビジョンは逆の順序として扱
776 776 われます。
777 777
778 778 範囲は閉区間として動作します。これは範囲が 3:5 は 3,4,5 になる
779 779 ことを意味します。同様に、範囲 4:2 は 4,3,2 になります。
780 780
781 781 環境変数
782 782 ----
783 783
784 784 HGEDITOR::
785 785 これはコミッチ時に使われるエディタの名前です。デフォルトでは
786 786 EDITOR の値が使われます。
787 787
788 788 (廃止予定です, .hgrc を使ってください)
789 789
790 790 HGMERGE::
791 791 merge 時の衝突を解決するのに使われる実行ファイルです。プログラ
792 792 ムは3 つの引数で実行されます: ローカルのファイル、リモートのファ
793 793 イル、1 世代前のファイルです。
794 794
795 795 デフォルトのプログラムは "hgmerge" で、これは Mercurial によっ
796 796 て提供される常識的な設定のシェルスクリプトです。
797 797
798 798 (廃止予定です, .hgrc を使ってください)
799 799
800 800 HGUSER::
801 801 これはコミット時の著者として使われる文字列です。
802 802
803 803 (廃止予定です, .hgrc を使ってください)
804 804
805 805 EMAIL::
806 806 もし HGUSER が設定されていなければ、これがコミット時の著者とし
807 807 て使われます。
808 808
809 809 LOGNAME::
810 810 もし HGUSER も EMAIL も設定されていなければ、コミット時の著者
811 811 としてLOGNAME が('@hostname' を付けた形で)使われます。
812 812
813 813 EDITOR::
814 814 これは hgmerge スクリプト中で使われるエディタの名前です。もし
815 815 HGEDITOR が設定されていなければ、コミット時のメッセージに使わ
816 816 れます。デフォルトは 'vi' です。
817 817
818 818 PYTHONPATH::
819 819 これはインポートされるモジュールを見つけるために Python によっ
820 820 て使われ、Mercurial がシステム全体にインストールされていなけれ
821 821 ば、適切に設定される必要があるでしょう。
822 822
823 823 ファイル
824 824 ----
825 825 .hgignore::
826 826 このファイルは(1行ごとに) hg によって無視されるべきファイルを
827 827 記述した正規表現を含みます。
828 828
829 829 .hgtags::
830 830 このファイルはリポジトリの内容のタグ付けされたバージョンに一致
831 831 したハッシュ値とテキストのタグ名(それぞれは空白で区切られます)を
832 832 含みます。
833 833
834 834 /etc/mercurial/hgrc, $HOME/.hgrc, .hg/hgrc::
835 835 このファイルはデフォルトの設定を含みます。.hg/hgrc の値は
836 836 $HOME/.hgrc の設定を上書きし、$HOME/.hgrc の設定はグローバルな
837 837 /etc/mercurial/hgrc の設定を上書きします。これらのファイルの内
838 838 容と書式の詳細については hgrc(5) を参照してください。
839 839
840 840 バグ
841 841 --
842 842 沢山あるでしょうから、もしバグを見つけたらそれをメーリングリスト
843 843 (下の情報源を参照)に送ってください。
844 844
845 845 関連項目
846 846 ----
847 847 hgrc(5)
848 848
849 849 著者
850 850 --
851 851 Matt Mackall <mpm@selenic.com> により書かれました。
852 852
853 853 情報源
854 854 ---
855 855 http://selenic.com/mercurial[主なウェブサイト]
856 856
857 857 http://www.serpentine.com/mercurial[Wiki サイト]
858 858
859 859 http://selenic.com/hg[ソースコードのリポジトリ]
860 860
861 861 http://selenic.com/mailman/listinfo/mercurial[メーリングリスト]
862 862
863 863 著作権情報
864 864 -----
865 Copyright (C) 2005 Matt Mackall.
865 Copyright (C) 2005, 2006 Matt Mackall.
866 866 このソフトウェアの自由な使用は GNU 一般公有使用許諾 (GPL) のもとで
867 867 認められます。
@@ -1,37 +1,37
1 1 HGMERGE(1)
2 2 ==========
3 3 Matt Mackall <mpm@selenic.com>
4 4 v0.1, 27 May 2005
5 5
6 6 名前
7 7 --
8 8 hgmerge - Mercurial ソースコード管理システムでファイルをマージする
9 9 のに使われるデフォルトのラッパー
10 10
11 11 書式
12 12 --
13 13 'hgmerge' local ancestor remote
14 14
15 15 説明
16 16 --
17 17 hgmerge(1) コマンドは Mercurial システムでファイルをマージするため
18 18 のグラフィカルなインターフェイスを提供します。これは kdiff3,
19 19 merge(1), tkdiff(1), または単純に diff(1) と patch(1) のラッパーで、
20 20 どれがシステム上にあるかに依存します。
21 21
22 22 hgmerge(1) は Mercurial ソースコード管理システムで環境変数
23 23 HGMERGE が設定されていない場合に使われます。
24 24
25 25 著者
26 26 --
27 27 Vincent Danjean <Vincent.Danjean@free.fr> によって書かれました。
28 28
29 29 関連情報
30 30 --
31 31 hg(1) - Mercurial システムへのコマンドラインインターフェイス
32 32
33 33 著作権情報
34 34 ----
35 Copyright (C) 2005 Matt Mackall.
35 Copyright (C) 2005, 2006 Matt Mackall.
36 36 このソフトウェアの自由な使用は GNU 一般公有使用許諾 (GPL) のもとで
37 37 認められます。
@@ -1,150 +1,175
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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7 #
8 # allow to use external programs to compare revisions, or revision
9 # with working dir. program is called with two arguments: paths to
10 # directories containing snapshots of files to compare.
8 # The `extdiff' Mercurial extension allows you to use external programs
9 # to compare revisions, or revision with working dir. The external diff
10 # programs are called with a configurable set of options and two
11 # non-option arguments: paths to directories containing snapshots of
12 # files to compare.
11 13 #
12 # to enable:
14 # To enable this extension:
13 15 #
14 16 # [extensions]
15 17 # hgext.extdiff =
16 18 #
17 # also allows to configure new diff commands, so you do not need to
18 # type "hg extdiff -p kdiff3" always.
19 # The `extdiff' extension also allows to configure new diff commands, so
20 # you do not need to type "hg extdiff -p kdiff3" always.
19 21 #
20 22 # [extdiff]
23 # # add new command that runs GNU diff(1) in 'context diff' mode
24 # cmd.cdiff = gdiff
25 # opts.cdiff = -Nprc5
21 26 # # add new command called vdiff, runs kdiff3
22 27 # cmd.vdiff = kdiff3
23 28 # # add new command called meld, runs meld (no need to name twice)
24 29 # cmd.meld =
30 # # add new command called vimdiff, runs gvimdiff with DirDiff plugin
31 # #(see http://www.vim.org/scripts/script.php?script_id=102)
32 # cmd.vimdiff = LC_ALL=C gvim -f '+bdel 1 2' '+ execute "DirDiff ".argv(0)." ".argv(1)'
25 33 #
26 # you can use -I/-X and list of file or directory names like normal
27 # "hg diff" command. extdiff makes snapshots of only needed files, so
28 # compare program will be fast.
34 # Each custom diff commands can have two parts: a `cmd' and an `opts'
35 # part. The cmd.xxx option defines the name of an executable program
36 # that will be run, and opts.xxx defines a set of command-line options
37 # which will be inserted to the command between the program name and
38 # the files/directories to diff (i.e. the cdiff example above).
39 #
40 # You can use -I/-X and list of file or directory names like normal
41 # "hg diff" command. The `extdiff' extension makes snapshots of only
42 # needed files, so running the external diff program will actually be
43 # pretty fast (at least faster than having to compare the entire tree).
29 44
30 45 from mercurial.demandload import demandload
31 46 from mercurial.i18n import gettext as _
32 47 from mercurial.node import *
33 demandload(globals(), 'mercurial:commands,util os shutil tempfile')
48 demandload(globals(), 'mercurial:commands,cmdutil,util os shutil tempfile')
34 49
35 def dodiff(ui, repo, diffcmd, pats, opts):
50 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
36 51 def snapshot_node(files, node):
37 52 '''snapshot files as of some revision'''
38 53 changes = repo.changelog.read(node)
39 54 mf = repo.manifest.read(changes[0])
40 55 dirname = '%s.%s' % (os.path.basename(repo.root), short(node))
41 56 base = os.path.join(tmproot, dirname)
42 57 os.mkdir(base)
43 58 if not ui.quiet:
44 59 ui.write_err(_('making snapshot of %d files from rev %s\n') %
45 60 (len(files), short(node)))
46 61 for fn in files:
47 62 wfn = util.pconvert(fn)
48 63 ui.note(' %s\n' % wfn)
49 64 dest = os.path.join(base, wfn)
50 65 destdir = os.path.dirname(dest)
51 66 if not os.path.isdir(destdir):
52 67 os.makedirs(destdir)
53 68 repo.wwrite(wfn, repo.file(fn).read(mf[fn]), open(dest, 'w'))
54 69 return dirname
55 70
56 71 def snapshot_wdir(files):
57 72 '''snapshot files from working directory.
58 73 if not using snapshot, -I/-X does not work and recursive diff
59 74 in tools like kdiff3 and meld displays too many files.'''
60 75 dirname = os.path.basename(repo.root)
61 76 base = os.path.join(tmproot, dirname)
62 77 os.mkdir(base)
63 78 if not ui.quiet:
64 79 ui.write_err(_('making snapshot of %d files from working dir\n') %
65 80 (len(files)))
66 81 for fn in files:
67 82 wfn = util.pconvert(fn)
68 83 ui.note(' %s\n' % wfn)
69 84 dest = os.path.join(base, wfn)
70 85 destdir = os.path.dirname(dest)
71 86 if not os.path.isdir(destdir):
72 87 os.makedirs(destdir)
73 88 fp = open(dest, 'w')
74 89 for chunk in util.filechunkiter(repo.wopener(wfn)):
75 90 fp.write(chunk)
76 91 return dirname
77 92
78 93 node1, node2 = commands.revpair(ui, repo, opts['rev'])
79 files, matchfn, anypats = commands.matchpats(repo, pats, opts)
80 modified, added, removed, deleted, unknown = repo.changes(
81 node1, node2, files, match=matchfn)
94 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
95 modified, added, removed, deleted, unknown = repo.status(
96 node1, node2, files, match=matchfn)[:5]
82 97 if not (modified or added or removed):
83 98 return 0
84 99
85 100 tmproot = tempfile.mkdtemp(prefix='extdiff.')
86 101 try:
87 102 dir1 = snapshot_node(modified + removed, node1)
88 103 if node2:
89 104 dir2 = snapshot_node(modified + added, node2)
90 105 else:
91 106 dir2 = snapshot_wdir(modified + added)
92 util.system('%s %s "%s" "%s"' %
93 (diffcmd, ' '.join(opts['option']), dir1, dir2),
94 cwd=tmproot)
107 cmdline = ('%s %s %s %s' %
108 (util.shellquote(diffcmd),
109 ' '.join(map(util.shellquote, diffopts)),
110 util.shellquote(dir1), util.shellquote(dir2)))
111 ui.debug('running %r in %s\n' % (cmdline, tmproot))
112 util.system(cmdline, cwd=tmproot)
95 113 return 1
96 114 finally:
97 115 ui.note(_('cleaning up temp directory\n'))
98 116 shutil.rmtree(tmproot)
99 117
100 118 def extdiff(ui, repo, *pats, **opts):
101 119 '''use external program to diff repository (or selected files)
102 120
103 121 Show differences between revisions for the specified files, using
104 an external program. The default program used is "diff -Npru".
122 an external program. The default program used is diff, with
123 default options "-Npru".
124
105 125 To select a different program, use the -p option. The program
106 126 will be passed the names of two directories to compare. To pass
107 127 additional options to the program, use the -o option. These will
108 128 be passed before the names of the directories to compare.
109 129
110 130 When two revision arguments are given, then changes are
111 131 shown between those revisions. If only one revision is
112 132 specified then that revision is compared to the working
113 133 directory, and, when no revisions are specified, the
114 134 working directory files are compared to its parent.'''
115 return dodiff(ui, repo, opts['program'] or 'diff -Npru', pats, opts)
135 return dodiff(ui, repo, opts['program'] or 'diff',
136 opts['option'] or ['-Npru'], pats, opts)
116 137
117 138 cmdtable = {
118 139 "extdiff":
119 140 (extdiff,
120 141 [('p', 'program', '', _('comparison program to run')),
121 142 ('o', 'option', [], _('pass option to comparison program')),
122 143 ('r', 'rev', [], _('revision')),
123 144 ('I', 'include', [], _('include names matching the given patterns')),
124 145 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
125 146 _('hg extdiff [OPT]... [FILE]...')),
126 147 }
127 148
128 149 def uisetup(ui):
129 150 for cmd, path in ui.configitems('extdiff'):
130 151 if not cmd.startswith('cmd.'): continue
131 152 cmd = cmd[4:]
132 153 if not path: path = cmd
154 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
155 diffopts = diffopts and [diffopts] or []
133 156 def save(cmd, path):
134 157 '''use closure to save diff command to use'''
135 158 def mydiff(ui, repo, *pats, **opts):
136 return dodiff(ui, repo, path, pats, opts)
137 mydiff.__doc__ = '''use %s to diff repository (or selected files)
159 return dodiff(ui, repo, path, diffopts, pats, opts)
160 mydiff.__doc__ = '''use %(path)r to diff repository (or selected files)
138 161
139 162 Show differences between revisions for the specified
140 files, using the %s program.
163 files, using the %(path)r program.
141 164
142 165 When two revision arguments are given, then changes are
143 166 shown between those revisions. If only one revision is
144 167 specified then that revision is compared to the working
145 168 directory, and, when no revisions are specified, the
146 working directory files are compared to its parent.''' % (cmd, cmd)
169 working directory files are compared to its parent.''' % {
170 'path': path,
171 }
147 172 return mydiff
148 173 cmdtable[cmd] = (save(cmd, path),
149 174 cmdtable['extdiff'][1][1:],
150 175 _('hg %s [OPT]... [FILE]...') % cmd)
@@ -1,269 +1,269
1 1 # GnuPG signing extension for Mercurial
2 2 #
3 3 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import os, tempfile, binascii
9 9 from mercurial import util
10 10 from mercurial import node as hgnode
11 11 from mercurial.i18n import gettext as _
12 12
13 13 class gpg:
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 fl = repo.file(".hgsigs")
94 94 h = fl.heads()
95 95 h.reverse()
96 96 # read the heads
97 97 for r in h:
98 98 fn = ".hgsigs|%s" % hgnode.short(r)
99 99 for item in parsefile(fl.read(r).splitlines(), fn):
100 100 yield item
101 101 try:
102 102 # read local signatures
103 103 fn = "localsigs"
104 104 for item in parsefile(repo.opener(fn), fn):
105 105 yield item
106 106 except IOError:
107 107 pass
108 108
109 109 def getkeys(ui, repo, mygpg, sigdata, context):
110 110 """get the keys who signed a data"""
111 111 fn, ln = context
112 112 node, version, sig = sigdata
113 113 prefix = "%s:%d" % (fn, ln)
114 114 node = hgnode.bin(node)
115 115
116 116 data = node2txt(repo, node, version)
117 117 sig = binascii.a2b_base64(sig)
118 118 err, keys = mygpg.verify(data, sig)
119 119 if err:
120 120 ui.warn("%s:%d %s\n" % (fn, ln , err))
121 121 return None
122 122
123 123 validkeys = []
124 124 # warn for expired key and/or sigs
125 125 for key in keys:
126 126 if key[0] == "BADSIG":
127 127 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
128 128 continue
129 129 if key[0] == "EXPSIG":
130 130 ui.write(_("%s Note: Signature has expired"
131 131 " (signed by: \"%s\")\n") % (prefix, key[2]))
132 132 elif key[0] == "EXPKEYSIG":
133 133 ui.write(_("%s Note: This key has expired"
134 134 " (signed by: \"%s\")\n") % (prefix, key[2]))
135 135 validkeys.append((key[1], key[2], key[3]))
136 136 return validkeys
137 137
138 138 def sigs(ui, repo):
139 139 """list signed changesets"""
140 140 mygpg = newgpg(ui)
141 141 revs = {}
142 142
143 143 for data, context in sigwalk(repo):
144 144 node, version, sig = data
145 145 fn, ln = context
146 146 try:
147 147 n = repo.lookup(node)
148 148 except KeyError:
149 149 ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
150 150 continue
151 151 r = repo.changelog.rev(n)
152 152 keys = getkeys(ui, repo, mygpg, data, context)
153 153 if not keys:
154 154 continue
155 155 revs.setdefault(r, [])
156 156 revs[r].extend(keys)
157 157 nodes = list(revs)
158 158 nodes.reverse()
159 159 for rev in nodes:
160 160 for k in revs[rev]:
161 161 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
162 162 ui.write("%-30s %s\n" % (keystr(ui, k), r))
163 163
164 164 def check(ui, repo, rev):
165 165 """verify all the signatures there may be for a particular revision"""
166 166 mygpg = newgpg(ui)
167 167 rev = repo.lookup(rev)
168 168 hexrev = hgnode.hex(rev)
169 169 keys = []
170 170
171 171 for data, context in sigwalk(repo):
172 172 node, version, sig = data
173 173 if node == hexrev:
174 174 k = getkeys(ui, repo, mygpg, data, context)
175 175 if k:
176 176 keys.extend(k)
177 177
178 178 if not keys:
179 179 ui.write(_("No valid signature for %s\n") % hgnode.short(rev))
180 180 return
181 181
182 182 # print summary
183 183 ui.write("%s is signed by:\n" % hgnode.short(rev))
184 184 for key in keys:
185 185 ui.write(" %s\n" % keystr(ui, key))
186 186
187 187 def keystr(ui, key):
188 188 """associate a string to a key (username, comment)"""
189 189 keyid, user, fingerprint = key
190 190 comment = ui.config("gpg", fingerprint, None)
191 191 if comment:
192 192 return "%s (%s)" % (user, comment)
193 193 else:
194 194 return user
195 195
196 196 def sign(ui, repo, *revs, **opts):
197 197 """add a signature for the current tip or a given revision"""
198 198 mygpg = newgpg(ui, **opts)
199 199 sigver = "0"
200 200 sigmessage = ""
201 201 if revs:
202 202 nodes = [repo.lookup(n) for n in revs]
203 203 else:
204 204 nodes = [repo.changelog.tip()]
205 205
206 206 for n in nodes:
207 207 hexnode = hgnode.hex(n)
208 208 ui.write("Signing %d:%s\n" % (repo.changelog.rev(n),
209 209 hgnode.short(n)))
210 210 # build data
211 211 data = node2txt(repo, n, sigver)
212 212 sig = mygpg.sign(data)
213 213 if not sig:
214 214 raise util.Abort(_("Error while signing"))
215 215 sig = binascii.b2a_base64(sig)
216 216 sig = sig.replace("\n", "")
217 217 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
218 218
219 219 # write it
220 220 if opts['local']:
221 221 repo.opener("localsigs", "ab").write(sigmessage)
222 222 return
223 223
224 for x in repo.changes():
224 for x in repo.status()[:5]:
225 225 if ".hgsigs" in x and not opts["force"]:
226 226 raise util.Abort(_("working copy of .hgsigs is changed "
227 227 "(please commit .hgsigs manually "
228 228 "or use --force)"))
229 229
230 230 repo.wfile(".hgsigs", "ab").write(sigmessage)
231 231
232 232 if repo.dirstate.state(".hgsigs") == '?':
233 233 repo.add([".hgsigs"])
234 234
235 235 if opts["no_commit"]:
236 236 return
237 237
238 238 message = opts['message']
239 239 if not message:
240 240 message = "\n".join([_("Added signature for changeset %s")
241 241 % hgnode.hex(n)
242 242 for n in nodes])
243 243 try:
244 244 repo.commit([".hgsigs"], message, opts['user'], opts['date'])
245 245 except ValueError, inst:
246 246 raise util.Abort(str(inst))
247 247
248 248 def node2txt(repo, node, ver):
249 249 """map a manifest into some text"""
250 250 if ver == "0":
251 251 return "%s\n" % hgnode.hex(node)
252 252 else:
253 253 raise util.Abort(_("unknown signature version"))
254 254
255 255 cmdtable = {
256 256 "sign":
257 257 (sign,
258 258 [('l', 'local', None, _("make the signature local")),
259 259 ('f', 'force', None, _("sign even if the sigfile is modified")),
260 260 ('', 'no-commit', None, _("do not commit the sigfile after signing")),
261 261 ('m', 'message', "", _("commit message")),
262 262 ('d', 'date', "", _("date code")),
263 263 ('u', 'user', "", _("user")),
264 264 ('k', 'key', "", _("the key id to sign with"))],
265 265 _("hg sign [OPTION]... [REVISION]...")),
266 266 "sigcheck": (check, [], _('hg sigcheck REVISION')),
267 267 "sigs": (sigs, [], _('hg sigs')),
268 268 }
269 269
@@ -1,296 +1,299
1 1 # bisect extension for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
4 4 # Inspired by git bisect, extension skeleton taken from mq.py.
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 from mercurial.i18n import gettext as _
10 10 from mercurial.demandload import demandload
11 11 demandload(globals(), "os sys sets mercurial:hg,util,commands")
12 12
13 13 versionstr = "0.0.3"
14 14
15 15 def lookup_rev(ui, repo, rev=None):
16 16 """returns rev or the checked-out revision if rev is None"""
17 17 if not rev is None:
18 18 return repo.lookup(rev)
19 19 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
20 20 if len(parents) != 1:
21 21 raise util.Abort(_("unexpected number of parents, "
22 22 "please commit or revert"))
23 23 return parents.pop()
24 24
25 25 def check_clean(ui, repo):
26 modified, added, removed, deleted, unknown = repo.changes()
27 if modified or added or removed:
28 ui.warn("Repository is not clean, please commit or revert\n")
29 sys.exit(1)
26 modified, added, removed, deleted, unknown = repo.status()[:5]
27 if modified or added or removed:
28 ui.warn("Repository is not clean, please commit or revert\n")
29 sys.exit(1)
30 30
31 31 class bisect(object):
32 32 """dichotomic search in the DAG of changesets"""
33 33 def __init__(self, ui, repo):
34 34 self.repo = repo
35 35 self.path = repo.join("bisect")
36 36 self.opener = util.opener(self.path)
37 37 self.ui = ui
38 38 self.goodrevs = []
39 39 self.badrev = None
40 40 self.good_dirty = 0
41 41 self.bad_dirty = 0
42 42 self.good_path = "good"
43 43 self.bad_path = "bad"
44 44
45 45 if os.path.exists(os.path.join(self.path, self.good_path)):
46 46 self.goodrevs = self.opener(self.good_path).read().splitlines()
47 47 self.goodrevs = [hg.bin(x) for x in self.goodrevs]
48 48 if os.path.exists(os.path.join(self.path, self.bad_path)):
49 49 r = self.opener(self.bad_path).read().splitlines()
50 50 if r:
51 51 self.badrev = hg.bin(r.pop(0))
52 52
53 def __del__(self):
53 def write(self):
54 54 if not os.path.isdir(self.path):
55 55 return
56 56 f = self.opener(self.good_path, "w")
57 57 f.write("\n".join([hg.hex(r) for r in self.goodrevs]))
58 58 if len(self.goodrevs) > 0:
59 59 f.write("\n")
60 60 f = self.opener(self.bad_path, "w")
61 61 if self.badrev:
62 62 f.write(hg.hex(self.badrev) + "\n")
63 63
64 64 def init(self):
65 65 """start a new bisection"""
66 66 if os.path.isdir(self.path):
67 67 raise util.Abort(_("bisect directory already exists\n"))
68 68 os.mkdir(self.path)
69 69 check_clean(self.ui, self.repo)
70 70 return 0
71 71
72 72 def reset(self):
73 73 """finish a bisection"""
74 74 if os.path.isdir(self.path):
75 75 sl = [os.path.join(self.path, p)
76 76 for p in [self.bad_path, self.good_path]]
77 77 for s in sl:
78 78 if os.path.exists(s):
79 79 os.unlink(s)
80 80 os.rmdir(self.path)
81 81 # Not sure about this
82 82 #self.ui.write("Going back to tip\n")
83 83 #self.repo.update(self.repo.changelog.tip())
84 84 return 1
85 85
86 86 def num_ancestors(self, head=None, stop=None):
87 87 """
88 88 returns a dict with the mapping:
89 89 node -> number of ancestors (self included)
90 90 for all nodes who are ancestor of head and
91 91 not in stop.
92 92 """
93 93 if head is None:
94 94 head = self.badrev
95 95 return self.__ancestors_and_nb_ancestors(head, stop)[1]
96 96
97 97 def ancestors(self, head=None, stop=None):
98 98 """
99 99 returns the set of the ancestors of head (self included)
100 100 who are not in stop.
101 101 """
102 102 if head is None:
103 103 head = self.badrev
104 104 return self.__ancestors_and_nb_ancestors(head, stop)[0]
105 105
106 106 def __ancestors_and_nb_ancestors(self, head, stop=None):
107 107 """
108 108 if stop is None then ancestors of goodrevs are used as
109 109 lower limit.
110 110
111 111 returns (anc, n_child) where anc is the set of the ancestors of head
112 112 and n_child is a dictionary with the following mapping:
113 113 node -> number of ancestors (self included)
114 114 """
115 115 cl = self.repo.changelog
116 116 if not stop:
117 117 stop = sets.Set([])
118 118 for i in xrange(len(self.goodrevs)-1, -1, -1):
119 119 g = self.goodrevs[i]
120 120 if g in stop:
121 121 continue
122 122 stop.update(cl.reachable(g))
123 123 def num_children(a):
124 124 """
125 125 returns a dictionnary with the following mapping
126 126 node -> [number of children, empty set]
127 127 """
128 128 d = {a: [0, sets.Set([])]}
129 129 for i in xrange(cl.rev(a)+1):
130 130 n = cl.node(i)
131 131 if not d.has_key(n):
132 132 d[n] = [0, sets.Set([])]
133 133 parents = [p for p in cl.parents(n) if p != hg.nullid]
134 134 for p in parents:
135 135 d[p][0] += 1
136 136 return d
137 137
138 138 if head in stop:
139 139 raise util.Abort(_("Unconsistent state, %s:%s is good and bad")
140 140 % (cl.rev(head), hg.short(head)))
141 141 n_child = num_children(head)
142 142 for i in xrange(cl.rev(head)+1):
143 143 n = cl.node(i)
144 144 parents = [p for p in cl.parents(n) if p != hg.nullid]
145 145 for p in parents:
146 146 n_child[p][0] -= 1
147 147 if not n in stop:
148 148 n_child[n][1].union_update(n_child[p][1])
149 149 if n_child[p][0] == 0:
150 150 n_child[p] = len(n_child[p][1])
151 151 if not n in stop:
152 152 n_child[n][1].add(n)
153 153 if n_child[n][0] == 0:
154 154 if n == head:
155 155 anc = n_child[n][1]
156 156 n_child[n] = len(n_child[n][1])
157 157 return anc, n_child
158 158
159 159 def next(self):
160 160 if not self.badrev:
161 161 raise util.Abort(_("You should give at least one bad revision"))
162 162 if not self.goodrevs:
163 163 self.ui.warn(_("No good revision given\n"))
164 164 self.ui.warn(_("Marking the first revision as good\n"))
165 165 ancestors, num_ancestors = self.__ancestors_and_nb_ancestors(
166 166 self.badrev)
167 167 tot = len(ancestors)
168 168 if tot == 1:
169 169 if ancestors.pop() != self.badrev:
170 170 raise util.Abort(_("Could not find the first bad revision"))
171 171 self.ui.write(_("The first bad revision is:\n"))
172 172 displayer = commands.show_changeset(self.ui, self.repo, {})
173 173 displayer.show(changenode=self.badrev)
174 174 return None
175 175 best_rev = None
176 176 best_len = -1
177 177 for n in ancestors:
178 178 l = num_ancestors[n]
179 179 l = min(l, tot - l)
180 180 if l > best_len:
181 181 best_len = l
182 182 best_rev = n
183 183 assert best_rev is not None
184 184 nb_tests = 0
185 185 q, r = divmod(tot, 2)
186 186 while q:
187 187 nb_tests += 1
188 188 q, r = divmod(q, 2)
189 189 msg = _("Testing changeset %s:%s (%s changesets remaining, "
190 190 "~%s tests)\n") % (self.repo.changelog.rev(best_rev),
191 191 hg.short(best_rev), tot, nb_tests)
192 192 self.ui.write(msg)
193 193 return best_rev
194 194
195 195 def autonext(self):
196 196 """find and update to the next revision to test"""
197 197 check_clean(self.ui, self.repo)
198 198 rev = self.next()
199 199 if rev is not None:
200 return self.repo.update(rev, force=True)
200 return hg.clean(self.repo, rev)
201 201
202 202 def good(self, rev):
203 203 self.goodrevs.append(rev)
204 204
205 205 def autogood(self, rev=None):
206 206 """mark revision as good and update to the next revision to test"""
207 207 check_clean(self.ui, self.repo)
208 208 rev = lookup_rev(self.ui, self.repo, rev)
209 209 self.good(rev)
210 210 if self.badrev:
211 211 return self.autonext()
212 212
213 213 def bad(self, rev):
214 214 self.badrev = rev
215 215
216 216 def autobad(self, rev=None):
217 217 """mark revision as bad and update to the next revision to test"""
218 218 check_clean(self.ui, self.repo)
219 219 rev = lookup_rev(self.ui, self.repo, rev)
220 220 self.bad(rev)
221 221 if self.goodrevs:
222 222 self.autonext()
223 223
224 224 # should we put it in the class ?
225 225 def test(ui, repo, rev):
226 226 """test the bisection code"""
227 227 b = bisect(ui, repo)
228 228 rev = repo.lookup(rev)
229 229 ui.write("testing with rev %s\n" % hg.hex(rev))
230 230 anc = b.ancestors()
231 231 while len(anc) > 1:
232 232 if not rev in anc:
233 233 ui.warn("failure while bisecting\n")
234 234 sys.exit(1)
235 235 ui.write("it worked :)\n")
236 236 new_rev = b.next()
237 237 ui.write("choosing if good or bad\n")
238 238 if rev in b.ancestors(head=new_rev):
239 239 b.bad(new_rev)
240 240 ui.write("it is bad\n")
241 241 else:
242 242 b.good(new_rev)
243 243 ui.write("it is good\n")
244 244 anc = b.ancestors()
245 245 #repo.update(new_rev, force=True)
246 246 for v in anc:
247 247 if v != rev:
248 248 ui.warn("fail to found cset! :(\n")
249 249 return 1
250 250 ui.write("Found bad cset: %s\n" % hg.hex(b.badrev))
251 251 ui.write("Everything is ok :)\n")
252 252 return 0
253 253
254 254 def bisect_run(ui, repo, cmd=None, *args):
255 255 """bisect extension: dichotomic search in the DAG of changesets
256 256 for subcommands see "hg bisect help\"
257 257 """
258 258 def help_(cmd=None, *args):
259 259 """show help for a given bisect subcommand or all subcommands"""
260 260 cmdtable = bisectcmdtable
261 261 if cmd:
262 262 doc = cmdtable[cmd][0].__doc__
263 263 synopsis = cmdtable[cmd][2]
264 264 ui.write(synopsis + "\n")
265 265 ui.write("\n" + doc + "\n")
266 266 return
267 267 ui.write(_("list of subcommands for the bisect extension\n\n"))
268 268 cmds = cmdtable.keys()
269 269 cmds.sort()
270 270 m = max([len(c) for c in cmds])
271 271 for cmd in cmds:
272 272 doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip()
273 273 ui.write(" %-*s %s\n" % (m, cmd, doc))
274 274
275 275 b = bisect(ui, repo)
276 276 bisectcmdtable = {
277 277 "init": (b.init, 0, _("hg bisect init")),
278 278 "bad": (b.autobad, 1, _("hg bisect bad [<rev>]")),
279 279 "good": (b.autogood, 1, _("hg bisect good [<rev>]")),
280 280 "next": (b.autonext, 0, _("hg bisect next")),
281 281 "reset": (b.reset, 0, _("hg bisect reset")),
282 282 "help": (help_, 1, _("hg bisect help [<subcommand>]")),
283 283 }
284 284
285 285 if not bisectcmdtable.has_key(cmd):
286 286 ui.warn(_("bisect: Unknown sub-command\n"))
287 287 return help_()
288 288 if len(args) > bisectcmdtable[cmd][1]:
289 289 ui.warn(_("bisect: Too many arguments\n"))
290 290 return help_()
291 return bisectcmdtable[cmd][0](*args)
291 try:
292 return bisectcmdtable[cmd][0](*args)
293 finally:
294 b.write()
292 295
293 296 cmdtable = {
294 297 "bisect": (bisect_run, [], _("hg bisect [help|init|reset|next|good|bad]")),
295 298 #"bisect-test": (test, [], "hg bisect-test rev"),
296 299 }
@@ -1,339 +1,337
1 1 # Minimal support for git commands on an hg repository
2 2 #
3 # Copyright 2005 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 import time, sys, signal, os
9 from mercurial import hg, mdiff, fancyopts, commands, ui, util
8 from mercurial.demandload import *
9 demandload(globals(), 'time sys signal os')
10 demandload(globals(), 'mercurial:hg,mdiff,fancyopts,commands,ui,util')
10 11
11 12 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
12 13 changes=None, text=False):
13 14 def date(c):
14 15 return time.asctime(time.gmtime(c[2][0]))
15 16
16 17 if not changes:
17 changes = repo.changes(node1, node2, files, match=match)
18 changes = repo.status(node1, node2, files, match=match)[:5]
18 19 modified, added, removed, deleted, unknown = changes
19 20 if files:
20 21 modified, added, removed = map(lambda x: filterfiles(files, x),
21 22 (modified, added, removed))
22 23
23 24 if not modified and not added and not removed:
24 25 return
25 26
26 27 if node2:
27 28 change = repo.changelog.read(node2)
28 29 mmap2 = repo.manifest.read(change[0])
29 30 date2 = date(change)
30 31 def read(f):
31 32 return repo.file(f).read(mmap2[f])
32 33 else:
33 34 date2 = time.asctime()
34 35 if not node1:
35 36 node1 = repo.dirstate.parents()[0]
36 37 def read(f):
37 38 return repo.wfile(f).read()
38 39
39 40 change = repo.changelog.read(node1)
40 41 mmap = repo.manifest.read(change[0])
41 42 date1 = date(change)
42 43
43 44 for f in modified:
44 45 to = None
45 46 if f in mmap:
46 47 to = repo.file(f).read(mmap[f])
47 48 tn = read(f)
48 49 fp.write("diff --git a/%s b/%s\n" % (f, f))
49 50 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
50 51 for f in added:
51 52 to = None
52 53 tn = read(f)
53 54 fp.write("diff --git /dev/null b/%s\n" % (f))
54 55 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
55 56 for f in removed:
56 57 to = repo.file(f).read(mmap[f])
57 58 tn = None
58 59 fp.write("diff --git a/%s /dev/null\n" % (f))
59 60 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
60 61
61 62 def difftree(ui, repo, node1=None, node2=None, **opts):
62 63 """diff trees from two commits"""
63 64 def __difftree(repo, node1, node2):
64 65 def date(c):
65 66 return time.asctime(time.gmtime(c[2][0]))
66 67
67 68 if node2:
68 69 change = repo.changelog.read(node2)
69 70 mmap2 = repo.manifest.read(change[0])
70 modified, added, removed, deleted, unknown = repo.changes(node1, node2)
71 modified, added, removed, deleted, unknown = repo.status(node1, node2)[:5]
71 72 def read(f): return repo.file(f).read(mmap2[f])
72 73 date2 = date(change)
73 74 else:
74 75 date2 = time.asctime()
75 modified, added, removed, deleted, unknown = repo.changes(node1)
76 modified, added, removed, deleted, unknown = repo.status(node1)[:5]
76 77 if not node1:
77 78 node1 = repo.dirstate.parents()[0]
78 79 def read(f): return file(os.path.join(repo.root, f)).read()
79 80
80 81 change = repo.changelog.read(node1)
81 82 mmap = repo.manifest.read(change[0])
82 83 date1 = date(change)
83 84 empty = "0" * 40;
84 85
85 86 for f in modified:
86 87 # TODO get file permissions
87 88 print ":100664 100664 %s %s M\t%s\t%s" % (hg.hex(mmap[f]),
88 89 hg.hex(mmap2[f]), f, f)
89 90 for f in added:
90 91 print ":000000 100664 %s %s N\t%s\t%s" % (empty, hg.hex(mmap2[f]), f, f)
91 92 for f in removed:
92 93 print ":100664 000000 %s %s D\t%s\t%s" % (hg.hex(mmap[f]), empty, f, f)
93 94 ##
94 95
95 96 while True:
96 97 if opts['stdin']:
97 98 try:
98 99 line = raw_input().split(' ')
99 100 node1 = line[0]
100 101 if len(line) > 1:
101 102 node2 = line[1]
102 103 else:
103 104 node2 = None
104 105 except EOFError:
105 106 break
106 107 node1 = repo.lookup(node1)
107 108 if node2:
108 109 node2 = repo.lookup(node2)
109 110 else:
110 111 node2 = node1
111 112 node1 = repo.changelog.parents(node1)[0]
112 113 if opts['patch']:
113 114 if opts['pretty']:
114 115 catcommit(repo, node2, "")
115 116 dodiff(sys.stdout, ui, repo, node1, node2)
116 117 else:
117 118 __difftree(repo, node1, node2)
118 119 if not opts['stdin']:
119 120 break
120 121
121 122 def catcommit(repo, n, prefix, changes=None):
122 123 nlprefix = '\n' + prefix;
123 124 (p1, p2) = repo.changelog.parents(n)
124 125 (h, h1, h2) = map(hg.hex, (n, p1, p2))
125 126 (i1, i2) = map(repo.changelog.rev, (p1, p2))
126 127 if not changes:
127 128 changes = repo.changelog.read(n)
128 129 print "tree %s" % (hg.hex(changes[0]))
129 130 if i1 != -1: print "parent %s" % (h1)
130 131 if i2 != -1: print "parent %s" % (h2)
131 132 date_ar = changes[2]
132 133 date = int(float(date_ar[0]))
133 134 lines = changes[4].splitlines()
134 135 if lines and lines[-1].startswith('committer:'):
135 136 committer = lines[-1].split(': ')[1].rstrip()
136 137 else:
137 138 committer = changes[1]
138 139
139 140 print "author %s %s %s" % (changes[1], date, date_ar[1])
140 141 print "committer %s %s %s" % (committer, date, date_ar[1])
141 142 print ""
142 143 if prefix != "":
143 144 print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
144 145 else:
145 146 print changes[4]
146 147 if prefix:
147 148 sys.stdout.write('\0')
148 149
149 150 def base(ui, repo, node1, node2):
150 151 """Output common ancestor information"""
151 152 node1 = repo.lookup(node1)
152 153 node2 = repo.lookup(node2)
153 154 n = repo.changelog.ancestor(node1, node2)
154 155 print hg.hex(n)
155 156
156 157 def catfile(ui, repo, type=None, r=None, **opts):
157 158 """cat a specific revision"""
158 159 # in stdin mode, every line except the commit is prefixed with two
159 160 # spaces. This way the our caller can find the commit without magic
160 161 # strings
161 162 #
162 163 prefix = ""
163 164 if opts['stdin']:
164 165 try:
165 166 (type, r) = raw_input().split(' ');
166 167 prefix = " "
167 168 except EOFError:
168 169 return
169 170
170 171 else:
171 172 if not type or not r:
172 173 ui.warn("cat-file: type or revision not supplied\n")
173 174 commands.help_(ui, 'cat-file')
174 175
175 176 while r:
176 177 if type != "commit":
177 178 sys.stderr.write("aborting hg cat-file only understands commits\n")
178 179 sys.exit(1);
179 180 n = repo.lookup(r)
180 181 catcommit(repo, n, prefix)
181 182 if opts['stdin']:
182 183 try:
183 184 (type, r) = raw_input().split(' ');
184 185 except EOFError:
185 186 break
186 187 else:
187 188 break
188 189
189 190 # git rev-tree is a confusing thing. You can supply a number of
190 191 # commit sha1s on the command line, and it walks the commit history
191 192 # telling you which commits are reachable from the supplied ones via
192 193 # a bitmask based on arg position.
193 194 # you can specify a commit to stop at by starting the sha1 with ^
194 195 def revtree(args, repo, full="tree", maxnr=0, parents=False):
195 196 def chlogwalk():
196 197 ch = repo.changelog
197 198 count = ch.count()
198 199 i = count
199 200 l = [0] * 100
200 201 chunk = 100
201 202 while True:
202 203 if chunk > i:
203 204 chunk = i
204 205 i = 0
205 206 else:
206 207 i -= chunk
207 208
208 209 for x in xrange(0, chunk):
209 210 if i + x >= count:
210 211 l[chunk - x:] = [0] * (chunk - x)
211 212 break
212 213 if full != None:
213 214 l[x] = ch.read(ch.node(i + x))
214 215 else:
215 216 l[x] = 1
216 217 for x in xrange(chunk-1, -1, -1):
217 218 if l[x] != 0:
218 219 yield (i + x, full != None and l[x] or None)
219 220 if i == 0:
220 221 break
221 222
222 223 # calculate and return the reachability bitmask for sha
223 224 def is_reachable(ar, reachable, sha):
224 225 if len(ar) == 0:
225 226 return 1
226 227 mask = 0
227 228 for i in range(len(ar)):
228 229 if sha in reachable[i]:
229 230 mask |= 1 << i
230 231
231 232 return mask
232 233
233 234 reachable = []
234 235 stop_sha1 = []
235 236 want_sha1 = []
236 237 count = 0
237 238
238 239 # figure out which commits they are asking for and which ones they
239 240 # want us to stop on
240 241 for i in range(len(args)):
241 242 if args[i].startswith('^'):
242 243 s = repo.lookup(args[i][1:])
243 244 stop_sha1.append(s)
244 245 want_sha1.append(s)
245 246 elif args[i] != 'HEAD':
246 247 want_sha1.append(repo.lookup(args[i]))
247 248
248 249 # calculate the graph for the supplied commits
249 250 for i in range(len(want_sha1)):
250 251 reachable.append({});
251 252 n = want_sha1[i];
252 253 visit = [n];
253 254 reachable[i][n] = 1
254 255 while visit:
255 256 n = visit.pop(0)
256 257 if n in stop_sha1:
257 258 continue
258 259 for p in repo.changelog.parents(n):
259 260 if p not in reachable[i]:
260 261 reachable[i][p] = 1
261 262 visit.append(p)
262 263 if p in stop_sha1:
263 264 continue
264 265
265 266 # walk the repository looking for commits that are in our
266 267 # reachability graph
267 268 #for i in range(repo.changelog.count()-1, -1, -1):
268 269 for i, changes in chlogwalk():
269 270 n = repo.changelog.node(i)
270 271 mask = is_reachable(want_sha1, reachable, n)
271 272 if mask:
272 273 parentstr = ""
273 274 if parents:
274 275 pp = repo.changelog.parents(n)
275 276 if pp[0] != hg.nullid:
276 277 parentstr += " " + hg.hex(pp[0])
277 278 if pp[1] != hg.nullid:
278 279 parentstr += " " + hg.hex(pp[1])
279 280 if not full:
280 281 print hg.hex(n) + parentstr
281 282 elif full is "commit":
282 283 print hg.hex(n) + parentstr
283 284 catcommit(repo, n, ' ', changes)
284 285 else:
285 286 (p1, p2) = repo.changelog.parents(n)
286 287 (h, h1, h2) = map(hg.hex, (n, p1, p2))
287 288 (i1, i2) = map(repo.changelog.rev, (p1, p2))
288 289
289 290 date = changes[2][0]
290 291 print "%s %s:%s" % (date, h, mask),
291 292 mask = is_reachable(want_sha1, reachable, p1)
292 293 if i1 != -1 and mask > 0:
293 294 print "%s:%s " % (h1, mask),
294 295 mask = is_reachable(want_sha1, reachable, p2)
295 296 if i2 != -1 and mask > 0:
296 297 print "%s:%s " % (h2, mask),
297 298 print ""
298 299 if maxnr and count >= maxnr:
299 300 break
300 301 count += 1
301 302
302 303 # git rev-list tries to order things by date, and has the ability to stop
303 304 # at a given commit without walking the whole repo. TODO add the stop
304 305 # parameter
305 306 def revlist(ui, repo, *revs, **opts):
306 307 """print revisions"""
307 308 if opts['header']:
308 309 full = "commit"
309 310 else:
310 311 full = None
311 312 copy = [x for x in revs]
312 313 revtree(copy, repo, full, opts['max_count'], opts['parents'])
313 314
314 315 def view(ui, repo, *etc):
315 316 "start interactive history viewer"
316 317 os.chdir(repo.root)
317 318 os.system(ui.config("hgk", "path", "hgk") + " " + " ".join(etc))
318 319
319 320 cmdtable = {
320 321 "view": (view, [], 'hg view'),
321 322 "debug-diff-tree": (difftree, [('p', 'patch', None, 'generate patch'),
322 323 ('r', 'recursive', None, 'recursive'),
323 324 ('P', 'pretty', None, 'pretty'),
324 325 ('s', 'stdin', None, 'stdin'),
325 326 ('C', 'copy', None, 'detect copies'),
326 327 ('S', 'search', "", 'search')],
327 328 "hg git-diff-tree [options] node1 node2"),
328 329 "debug-cat-file": (catfile, [('s', 'stdin', None, 'stdin')],
329 330 "hg debug-cat-file [options] type file"),
330 331 "debug-merge-base": (base, [], "hg debug-merge-base node node"),
331 332 "debug-rev-list": (revlist, [('H', 'header', None, 'header'),
332 333 ('t', 'topo-order', None, 'topo-order'),
333 334 ('p', 'parents', None, 'parents'),
334 335 ('n', 'max-count', 0, 'max-count')],
335 336 "hg debug-rev-list [options] revs"),
336 337 }
337
338 def reposetup(ui, repo):
339 pass
This diff has been collapsed as it changes many lines, (1338 lines changed) Show them Hide them
@@ -1,1346 +1,2004
1 1 # queue.py - patch queues for mercurial
2 2 #
3 # Copyright 2005 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 '''patch management and development
9 9
10 10 This extension lets you work with a stack of patches in a Mercurial
11 11 repository. It manages two stacks of patches - all known patches, and
12 12 applied patches (subset of known patches).
13 13
14 14 Known patches are represented as patch files in the .hg/patches
15 15 directory. Applied patches are both patch files and changesets.
16 16
17 17 Common tasks (use "hg help command" for more details):
18 18
19 19 prepare repository to work with patches qinit
20 20 create new patch qnew
21 21 import existing patch qimport
22 22
23 23 print patch series qseries
24 24 print applied patches qapplied
25 25 print name of top applied patch qtop
26 26
27 27 add known patch to applied stack qpush
28 28 remove patch from applied stack qpop
29 29 refresh contents of top applied patch qrefresh
30 30 '''
31 31
32 32 from mercurial.demandload import *
33 from mercurial.i18n import gettext as _
33 34 demandload(globals(), "os sys re struct traceback errno bz2")
34 from mercurial.i18n import gettext as _
35 from mercurial import ui, hg, revlog, commands, util
35 demandload(globals(), "mercurial:cmdutil,commands,hg,patch,revlog,ui,util")
36
37 commands.norepo += " qclone qversion"
36 38
37 versionstr = "0.45"
39 class statusentry:
40 def __init__(self, rev, name=None):
41 if not name:
42 fields = rev.split(':')
43 if len(fields) == 2:
44 self.rev, self.name = fields
45 else:
46 self.rev, self.name = None, None
47 else:
48 self.rev, self.name = rev, name
38 49
39 repomap = {}
50 def __str__(self):
51 return self.rev + ':' + self.name
40 52
41 commands.norepo += " qversion"
42 53 class queue:
43 54 def __init__(self, ui, path, patchdir=None):
44 55 self.basepath = path
45 if patchdir:
46 self.path = patchdir
47 else:
48 self.path = os.path.join(path, "patches")
56 self.path = patchdir or os.path.join(path, "patches")
49 57 self.opener = util.opener(self.path)
50 58 self.ui = ui
51 59 self.applied = []
52 60 self.full_series = []
53 61 self.applied_dirty = 0
54 62 self.series_dirty = 0
55 63 self.series_path = "series"
56 64 self.status_path = "status"
65 self.guards_path = "guards"
66 self.active_guards = None
67 self.guards_dirty = False
68 self._diffopts = None
57 69
58 if os.path.exists(os.path.join(self.path, self.series_path)):
70 if os.path.exists(self.join(self.series_path)):
59 71 self.full_series = self.opener(self.series_path).read().splitlines()
60 self.read_series(self.full_series)
72 self.parse_series()
73
74 if os.path.exists(self.join(self.status_path)):
75 lines = self.opener(self.status_path).read().splitlines()
76 self.applied = [statusentry(l) for l in lines]
61 77
62 if os.path.exists(os.path.join(self.path, self.status_path)):
63 self.applied = self.opener(self.status_path).read().splitlines()
78 def diffopts(self):
79 if self._diffopts is None:
80 self._diffopts = patch.diffopts(self.ui)
81 return self._diffopts
82
83 def join(self, *p):
84 return os.path.join(self.path, *p)
64 85
65 86 def find_series(self, patch):
66 87 pre = re.compile("(\s*)([^#]+)")
67 88 index = 0
68 89 for l in self.full_series:
69 90 m = pre.match(l)
70 91 if m:
71 92 s = m.group(2)
72 93 s = s.rstrip()
73 94 if s == patch:
74 95 return index
75 96 index += 1
76 97 return None
77 98
78 def read_series(self, list):
79 def matcher(list):
80 pre = re.compile("(\s*)([^#]+)")
81 for l in list:
82 m = pre.match(l)
83 if m:
84 s = m.group(2)
85 s = s.rstrip()
86 if len(s) > 0:
87 yield s
99 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
100
101 def parse_series(self):
88 102 self.series = []
89 self.series = [ x for x in matcher(list) ]
103 self.series_guards = []
104 for l in self.full_series:
105 h = l.find('#')
106 if h == -1:
107 patch = l
108 comment = ''
109 elif h == 0:
110 continue
111 else:
112 patch = l[:h]
113 comment = l[h:]
114 patch = patch.strip()
115 if patch:
116 self.series.append(patch)
117 self.series_guards.append(self.guard_re.findall(comment))
118
119 def check_guard(self, guard):
120 bad_chars = '# \t\r\n\f'
121 first = guard[0]
122 for c in '-+':
123 if first == c:
124 return (_('guard %r starts with invalid character: %r') %
125 (guard, c))
126 for c in bad_chars:
127 if c in guard:
128 return _('invalid character in guard %r: %r') % (guard, c)
129
130 def set_active(self, guards):
131 for guard in guards:
132 bad = self.check_guard(guard)
133 if bad:
134 raise util.Abort(bad)
135 guards = dict.fromkeys(guards).keys()
136 guards.sort()
137 self.ui.debug('active guards: %s\n' % ' '.join(guards))
138 self.active_guards = guards
139 self.guards_dirty = True
140
141 def active(self):
142 if self.active_guards is None:
143 self.active_guards = []
144 try:
145 guards = self.opener(self.guards_path).read().split()
146 except IOError, err:
147 if err.errno != errno.ENOENT: raise
148 guards = []
149 for i, guard in enumerate(guards):
150 bad = self.check_guard(guard)
151 if bad:
152 self.ui.warn('%s:%d: %s\n' %
153 (self.join(self.guards_path), i + 1, bad))
154 else:
155 self.active_guards.append(guard)
156 return self.active_guards
157
158 def set_guards(self, idx, guards):
159 for g in guards:
160 if len(g) < 2:
161 raise util.Abort(_('guard %r too short') % g)
162 if g[0] not in '-+':
163 raise util.Abort(_('guard %r starts with invalid char') % g)
164 bad = self.check_guard(g[1:])
165 if bad:
166 raise util.Abort(bad)
167 drop = self.guard_re.sub('', self.full_series[idx])
168 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
169 self.parse_series()
170 self.series_dirty = True
171
172 def pushable(self, idx):
173 if isinstance(idx, str):
174 idx = self.series.index(idx)
175 patchguards = self.series_guards[idx]
176 if not patchguards:
177 return True, None
178 default = False
179 guards = self.active()
180 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
181 if exactneg:
182 return False, exactneg[0]
183 pos = [g for g in patchguards if g[0] == '+']
184 exactpos = [g for g in pos if g[1:] in guards]
185 if pos:
186 if exactpos:
187 return True, exactpos[0]
188 return False, pos
189 return True, ''
190
191 def explain_pushable(self, idx, all_patches=False):
192 write = all_patches and self.ui.write or self.ui.warn
193 if all_patches or self.ui.verbose:
194 if isinstance(idx, str):
195 idx = self.series.index(idx)
196 pushable, why = self.pushable(idx)
197 if all_patches and pushable:
198 if why is None:
199 write(_('allowing %s - no guards in effect\n') %
200 self.series[idx])
201 else:
202 if not why:
203 write(_('allowing %s - no matching negative guards\n') %
204 self.series[idx])
205 else:
206 write(_('allowing %s - guarded by %r\n') %
207 (self.series[idx], why))
208 if not pushable:
209 if why:
210 write(_('skipping %s - guarded by %r\n') %
211 (self.series[idx], ' '.join(why)))
212 else:
213 write(_('skipping %s - no matching guards\n') %
214 self.series[idx])
90 215
91 216 def save_dirty(self):
92 if self.applied_dirty:
93 if len(self.applied) > 0:
94 nl = "\n"
95 else:
96 nl = ""
97 f = self.opener(self.status_path, "w")
98 f.write("\n".join(self.applied) + nl)
99 if self.series_dirty:
100 if len(self.full_series) > 0:
101 nl = "\n"
102 else:
103 nl = ""
104 f = self.opener(self.series_path, "w")
105 f.write("\n".join(self.full_series) + nl)
217 def write_list(items, path):
218 fp = self.opener(path, 'w')
219 for i in items:
220 print >> fp, i
221 fp.close()
222 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
223 if self.series_dirty: write_list(self.full_series, self.series_path)
224 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
106 225
107 226 def readheaders(self, patch):
108 227 def eatdiff(lines):
109 228 while lines:
110 229 l = lines[-1]
111 230 if (l.startswith("diff -") or
112 231 l.startswith("Index:") or
113 232 l.startswith("===========")):
114 233 del lines[-1]
115 234 else:
116 235 break
117 236 def eatempty(lines):
118 237 while lines:
119 238 l = lines[-1]
120 239 if re.match('\s*$', l):
121 240 del lines[-1]
122 241 else:
123 242 break
124 243
125 pf = os.path.join(self.path, patch)
244 pf = self.join(patch)
126 245 message = []
127 246 comments = []
128 247 user = None
129 248 date = None
130 249 format = None
131 250 subject = None
132 251 diffstart = 0
133 252
134 253 for line in file(pf):
135 254 line = line.rstrip()
255 if line.startswith('diff --git'):
256 diffstart = 2
257 break
136 258 if diffstart:
137 259 if line.startswith('+++ '):
138 260 diffstart = 2
139 261 break
140 262 if line.startswith("--- "):
141 263 diffstart = 1
142 264 continue
143 265 elif format == "hgpatch":
144 266 # parse values when importing the result of an hg export
145 267 if line.startswith("# User "):
146 268 user = line[7:]
147 269 elif line.startswith("# Date "):
148 270 date = line[7:]
149 271 elif not line.startswith("# ") and line:
150 272 message.append(line)
151 273 format = None
152 274 elif line == '# HG changeset patch':
153 275 format = "hgpatch"
154 276 elif (format != "tagdone" and (line.startswith("Subject: ") or
155 277 line.startswith("subject: "))):
156 278 subject = line[9:]
157 279 format = "tag"
158 280 elif (format != "tagdone" and (line.startswith("From: ") or
159 281 line.startswith("from: "))):
160 282 user = line[6:]
161 283 format = "tag"
162 284 elif format == "tag" and line == "":
163 285 # when looking for tags (subject: from: etc) they
164 286 # end once you find a blank line in the source
165 287 format = "tagdone"
166 288 elif message or line:
167 289 message.append(line)
168 290 comments.append(line)
169 291
170 292 eatdiff(message)
171 293 eatdiff(comments)
172 294 eatempty(message)
173 295 eatempty(comments)
174 296
175 297 # make sure message isn't empty
176 298 if format and format.startswith("tag") and subject:
177 299 message.insert(0, "")
178 300 message.insert(0, subject)
179 301 return (message, comments, user, date, diffstart > 1)
180 302
303 def printdiff(self, repo, node1, node2=None, files=None,
304 fp=None, changes=None, opts={}):
305 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
306
307 patch.diff(repo, node1, node2, fns, match=matchfn,
308 fp=fp, changes=changes, opts=self.diffopts())
309
181 310 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
182 311 # first try just applying the patch
183 312 (err, n) = self.apply(repo, [ patch ], update_status=False,
184 313 strict=True, merge=rev, wlock=wlock)
185 314
186 315 if err == 0:
187 316 return (err, n)
188 317
189 318 if n is None:
190 self.ui.warn("apply failed for patch %s\n" % patch)
191 sys.exit(1)
319 raise util.Abort(_("apply failed for patch %s") % patch)
192 320
193 321 self.ui.warn("patch didn't work out, merging %s\n" % patch)
194 322
195 323 # apply failed, strip away that rev and merge.
196 repo.update(head, allow=False, force=True, wlock=wlock)
324 hg.clean(repo, head, wlock=wlock)
197 325 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
198 326
199 327 c = repo.changelog.read(rev)
200 ret = repo.update(rev, allow=True, wlock=wlock)
328 ret = hg.merge(repo, rev, wlock=wlock)
201 329 if ret:
202 self.ui.warn("update returned %d\n" % ret)
203 sys.exit(1)
330 raise util.Abort(_("update returned %d") % ret)
204 331 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
205 332 if n == None:
206 self.ui.warn("repo commit failed\n")
207 sys.exit(1)
333 raise util.Abort(_("repo commit failed"))
208 334 try:
209 335 message, comments, user, date, patchfound = mergeq.readheaders(patch)
210 336 except:
211 self.ui.warn("Unable to read %s\n" % patch)
212 sys.exit(1)
337 raise util.Abort(_("unable to read %s") % patch)
213 338
214 339 patchf = self.opener(patch, "w")
215 340 if comments:
216 341 comments = "\n".join(comments) + '\n\n'
217 342 patchf.write(comments)
218 commands.dodiff(patchf, self.ui, repo, head, n)
343 self.printdiff(repo, head, n, fp=patchf)
219 344 patchf.close()
220 345 return (0, n)
221 346
222 347 def qparents(self, repo, rev=None):
223 348 if rev is None:
224 349 (p1, p2) = repo.dirstate.parents()
225 350 if p2 == revlog.nullid:
226 351 return p1
227 352 if len(self.applied) == 0:
228 353 return None
229 (top, patch) = self.applied[-1].split(':')
230 top = revlog.bin(top)
231 return top
354 return revlog.bin(self.applied[-1].rev)
232 355 pp = repo.changelog.parents(rev)
233 356 if pp[1] != revlog.nullid:
234 arevs = [ x.split(':')[0] for x in self.applied ]
357 arevs = [ x.rev for x in self.applied ]
235 358 p0 = revlog.hex(pp[0])
236 359 p1 = revlog.hex(pp[1])
237 360 if p0 in arevs:
238 361 return pp[0]
239 362 if p1 in arevs:
240 363 return pp[1]
241 364 return pp[0]
242 365
243 366 def mergepatch(self, repo, mergeq, series, wlock):
244 367 if len(self.applied) == 0:
245 368 # each of the patches merged in will have two parents. This
246 369 # can confuse the qrefresh, qdiff, and strip code because it
247 370 # needs to know which parent is actually in the patch queue.
248 371 # so, we insert a merge marker with only one parent. This way
249 372 # the first patch in the queue is never a merge patch
250 373 #
251 374 pname = ".hg.patches.merge.marker"
252 375 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
253 376 wlock=wlock)
254 self.applied.append(revlog.hex(n) + ":" + pname)
377 self.applied.append(statusentry(revlog.hex(n), pname))
255 378 self.applied_dirty = 1
256 379
257 380 head = self.qparents(repo)
258 381
259 382 for patch in series:
260 patch = mergeq.lookup(patch)
383 patch = mergeq.lookup(patch, strict=True)
261 384 if not patch:
262 385 self.ui.warn("patch %s does not exist\n" % patch)
263 386 return (1, None)
264
387 pushable, reason = self.pushable(patch)
388 if not pushable:
389 self.explain_pushable(patch, all_patches=True)
390 continue
265 391 info = mergeq.isapplied(patch)
266 392 if not info:
267 393 self.ui.warn("patch %s is not applied\n" % patch)
268 394 return (1, None)
269 395 rev = revlog.bin(info[1])
270 396 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
271 397 if head:
272 self.applied.append(revlog.hex(head) + ":" + patch)
398 self.applied.append(statusentry(revlog.hex(head), patch))
273 399 self.applied_dirty = 1
274 400 if err:
275 401 return (err, head)
276 402 return (0, head)
277 403
404 def patch(self, repo, patchfile):
405 '''Apply patchfile to the working directory.
406 patchfile: file name of patch'''
407 try:
408 (files, fuzz) = patch.patch(patchfile, self.ui, strip=1,
409 cwd=repo.root)
410 except Exception, inst:
411 self.ui.note(str(inst) + '\n')
412 if not self.ui.verbose:
413 self.ui.warn("patch failed, unable to continue (try -v)\n")
414 return (False, [], False)
415
416 return (True, files, fuzz)
417
278 418 def apply(self, repo, series, list=False, update_status=True,
279 419 strict=False, patchdir=None, merge=None, wlock=None):
280 420 # TODO unify with commands.py
281 421 if not patchdir:
282 422 patchdir = self.path
283 pwd = os.getcwd()
284 os.chdir(repo.root)
285 423 err = 0
286 424 if not wlock:
287 425 wlock = repo.wlock()
288 426 lock = repo.lock()
289 427 tr = repo.transaction()
290 428 n = None
291 for patch in series:
292 self.ui.warn("applying %s\n" % patch)
293 pf = os.path.join(patchdir, patch)
429 for patchname in series:
430 pushable, reason = self.pushable(patchname)
431 if not pushable:
432 self.explain_pushable(patchname, all_patches=True)
433 continue
434 self.ui.warn("applying %s\n" % patchname)
435 pf = os.path.join(patchdir, patchname)
294 436
295 437 try:
296 message, comments, user, date, patchfound = self.readheaders(patch)
438 message, comments, user, date, patchfound = self.readheaders(patchname)
297 439 except:
298 self.ui.warn("Unable to read %s\n" % pf)
440 self.ui.warn("Unable to read %s\n" % patchname)
299 441 err = 1
300 442 break
301 443
302 444 if not message:
303 message = "imported patch %s\n" % patch
445 message = "imported patch %s\n" % patchname
304 446 else:
305 447 if list:
306 message.append("\nimported patch %s" % patch)
448 message.append("\nimported patch %s" % patchname)
307 449 message = '\n'.join(message)
308 450
309 try:
310 pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
311 f = os.popen("%s -p1 --no-backup-if-mismatch < '%s'" % (pp, pf))
312 except:
313 self.ui.warn("patch failed, unable to continue (try -v)\n")
314 err = 1
315 break
316 files = []
317 fuzz = False
318 for l in f:
319 l = l.rstrip('\r\n');
320 if self.ui.verbose:
321 self.ui.warn(l + "\n")
322 if l[:14] == 'patching file ':
323 pf = os.path.normpath(l[14:])
324 # when patch finds a space in the file name, it puts
325 # single quotes around the filename. strip them off
326 if pf[0] == "'" and pf[-1] == "'":
327 pf = pf[1:-1]
328 if pf not in files:
329 files.append(pf)
330 printed_file = False
331 file_str = l
332 elif l.find('with fuzz') >= 0:
333 if not printed_file:
334 self.ui.warn(file_str + '\n')
335 printed_file = True
336 self.ui.warn(l + '\n')
337 fuzz = True
338 elif l.find('saving rejects to file') >= 0:
339 self.ui.warn(l + '\n')
340 elif l.find('FAILED') >= 0:
341 if not printed_file:
342 self.ui.warn(file_str + '\n')
343 printed_file = True
344 self.ui.warn(l + '\n')
345 patcherr = f.close()
451 (patcherr, files, fuzz) = self.patch(repo, pf)
452 patcherr = not patcherr
346 453
347 if merge and len(files) > 0:
454 if merge and files:
348 455 # Mark as merged and update dirstate parent info
349 repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
456 repo.dirstate.update(repo.dirstate.filterfiles(files.keys()), 'm')
350 457 p1, p2 = repo.dirstate.parents()
351 458 repo.dirstate.setparents(p1, merge)
352 if len(files) > 0:
353 commands.addremove_lock(self.ui, repo, files,
354 opts={}, wlock=wlock)
459 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
355 460 n = repo.commit(files, message, user, date, force=1, lock=lock,
356 461 wlock=wlock)
357 462
358 463 if n == None:
359 self.ui.warn("repo commit failed\n")
360 sys.exit(1)
464 raise util.Abort(_("repo commit failed"))
361 465
362 466 if update_status:
363 self.applied.append(revlog.hex(n) + ":" + patch)
467 self.applied.append(statusentry(revlog.hex(n), patchname))
364 468
365 469 if patcherr:
366 470 if not patchfound:
367 self.ui.warn("patch %s is empty\n" % patch)
471 self.ui.warn("patch %s is empty\n" % patchname)
368 472 err = 0
369 473 else:
370 474 self.ui.warn("patch failed, rejects left in working dir\n")
371 475 err = 1
372 476 break
373 477
374 478 if fuzz and strict:
375 479 self.ui.warn("fuzz found when applying patch, stopping\n")
376 480 err = 1
377 481 break
378 482 tr.close()
379 os.chdir(pwd)
380 483 return (err, n)
381 484
382 def delete(self, repo, patch):
383 patch = self.lookup(patch)
384 info = self.isapplied(patch)
385 if info:
386 self.ui.warn("cannot delete applied patch %s\n" % patch)
387 sys.exit(1)
388 if patch not in self.series:
389 self.ui.warn("patch %s not in series file\n" % patch)
390 sys.exit(1)
391 i = self.find_series(patch)
392 del self.full_series[i]
393 self.read_series(self.full_series)
485 def delete(self, repo, patches, keep=False):
486 realpatches = []
487 for patch in patches:
488 patch = self.lookup(patch, strict=True)
489 info = self.isapplied(patch)
490 if info:
491 raise util.Abort(_("cannot delete applied patch %s") % patch)
492 if patch not in self.series:
493 raise util.Abort(_("patch %s not in series file") % patch)
494 realpatches.append(patch)
495
496 if not keep:
497 r = self.qrepo()
498 if r:
499 r.remove(realpatches, True)
500 else:
501 os.unlink(self.join(patch))
502
503 indices = [self.find_series(p) for p in realpatches]
504 indices.sort()
505 for i in indices[-1::-1]:
506 del self.full_series[i]
507 self.parse_series()
394 508 self.series_dirty = 1
395 509
396 510 def check_toppatch(self, repo):
397 511 if len(self.applied) > 0:
398 (top, patch) = self.applied[-1].split(':')
399 top = revlog.bin(top)
512 top = revlog.bin(self.applied[-1].rev)
400 513 pp = repo.dirstate.parents()
401 514 if top not in pp:
402 self.ui.warn("queue top not at dirstate parents. top %s dirstate %s %s\n" %( revlog.short(top), revlog.short(pp[0]), revlog.short(pp[1])))
403 sys.exit(1)
515 raise util.Abort(_("queue top not at same revision as working directory"))
404 516 return top
405 517 return None
406 def check_localchanges(self, repo):
407 (c, a, r, d, u) = repo.changes(None, None)
408 if c or a or d or r:
409 self.ui.write("Local changes found, refresh first\n")
410 sys.exit(1)
518 def check_localchanges(self, repo, force=False, refresh=True):
519 m, a, r, d = repo.status()[:4]
520 if m or a or r or d:
521 if not force:
522 if refresh:
523 raise util.Abort(_("local changes found, refresh first"))
524 else:
525 raise util.Abort(_("local changes found"))
526 return m, a, r, d
411 527 def new(self, repo, patch, msg=None, force=None):
412 commitfiles = []
413 (c, a, r, d, u) = repo.changes(None, None)
414 if c or a or d or r:
415 if not force:
416 raise util.Abort(_("Local changes found, refresh first"))
417 else:
418 commitfiles = c + a + r
528 if os.path.exists(self.join(patch)):
529 raise util.Abort(_('patch "%s" already exists') % patch)
530 m, a, r, d = self.check_localchanges(repo, force)
531 commitfiles = m + a + r
419 532 self.check_toppatch(repo)
420 533 wlock = repo.wlock()
421 insert = self.series_end()
534 insert = self.full_series_end()
422 535 if msg:
423 536 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
424 537 wlock=wlock)
425 538 else:
426 539 n = repo.commit(commitfiles,
427 540 "New patch: %s" % patch, force=True, wlock=wlock)
428 541 if n == None:
429 self.ui.warn("repo commit failed\n")
430 sys.exit(1)
542 raise util.Abort(_("repo commit failed"))
431 543 self.full_series[insert:insert] = [patch]
432 self.applied.append(revlog.hex(n) + ":" + patch)
433 self.read_series(self.full_series)
544 self.applied.append(statusentry(revlog.hex(n), patch))
545 self.parse_series()
434 546 self.series_dirty = 1
435 547 self.applied_dirty = 1
436 548 p = self.opener(patch, "w")
437 549 if msg:
438 550 msg = msg + "\n"
439 551 p.write(msg)
440 552 p.close()
441 553 wlock = None
442 554 r = self.qrepo()
443 555 if r: r.add([patch])
444 556 if commitfiles:
445 557 self.refresh(repo, short=True)
446 558
447 559 def strip(self, repo, rev, update=True, backup="all", wlock=None):
448 560 def limitheads(chlog, stop):
449 561 """return the list of all nodes that have no children"""
450 562 p = {}
451 563 h = []
452 564 stoprev = 0
453 565 if stop in chlog.nodemap:
454 566 stoprev = chlog.rev(stop)
455 567
456 568 for r in range(chlog.count() - 1, -1, -1):
457 569 n = chlog.node(r)
458 570 if n not in p:
459 571 h.append(n)
460 572 if n == stop:
461 573 break
462 574 if r < stoprev:
463 575 break
464 576 for pn in chlog.parents(n):
465 577 p[pn] = 1
466 578 return h
467 579
468 580 def bundle(cg):
469 581 backupdir = repo.join("strip-backup")
470 582 if not os.path.isdir(backupdir):
471 583 os.mkdir(backupdir)
472 584 name = os.path.join(backupdir, "%s" % revlog.short(rev))
473 585 name = savename(name)
474 586 self.ui.warn("saving bundle to %s\n" % name)
475 587 # TODO, exclusive open
476 588 f = open(name, "wb")
477 589 try:
478 590 f.write("HG10")
479 591 z = bz2.BZ2Compressor(9)
480 592 while 1:
481 593 chunk = cg.read(4096)
482 594 if not chunk:
483 595 break
484 596 f.write(z.compress(chunk))
485 597 f.write(z.flush())
486 598 except:
487 599 os.unlink(name)
488 600 raise
489 601 f.close()
490 602 return name
491 603
492 604 def stripall(rev, revnum):
493 605 cl = repo.changelog
494 606 c = cl.read(rev)
495 607 mm = repo.manifest.read(c[0])
496 608 seen = {}
497 609
498 610 for x in xrange(revnum, cl.count()):
499 611 c = cl.read(cl.node(x))
500 612 for f in c[3]:
501 613 if f in seen:
502 614 continue
503 615 seen[f] = 1
504 616 if f in mm:
505 617 filerev = mm[f]
506 618 else:
507 619 filerev = 0
508 620 seen[f] = filerev
509 621 # we go in two steps here so the strip loop happens in a
510 622 # sensible order. When stripping many files, this helps keep
511 623 # our disk access patterns under control.
512 list = seen.keys()
513 list.sort()
514 for f in list:
624 seen_list = seen.keys()
625 seen_list.sort()
626 for f in seen_list:
515 627 ff = repo.file(f)
516 628 filerev = seen[f]
517 629 if filerev != 0:
518 630 if filerev in ff.nodemap:
519 631 filerev = ff.rev(filerev)
520 632 else:
521 633 filerev = 0
522 634 ff.strip(filerev, revnum)
523 635
524 636 if not wlock:
525 637 wlock = repo.wlock()
526 638 lock = repo.lock()
527 639 chlog = repo.changelog
528 640 # TODO delete the undo files, and handle undo of merge sets
529 641 pp = chlog.parents(rev)
530 642 revnum = chlog.rev(rev)
531 643
532 644 if update:
645 self.check_localchanges(repo, refresh=False)
533 646 urev = self.qparents(repo, rev)
534 repo.update(urev, allow=False, force=True, wlock=wlock)
647 hg.clean(repo, urev, wlock=wlock)
535 648 repo.dirstate.write()
536 649
537 650 # save is a list of all the branches we are truncating away
538 651 # that we actually want to keep. changegroup will be used
539 652 # to preserve them and add them back after the truncate
540 653 saveheads = []
541 654 savebases = {}
542 655
543 tip = chlog.tip()
544 656 heads = limitheads(chlog, rev)
545 657 seen = {}
546 658
547 659 # search through all the heads, finding those where the revision
548 660 # we want to strip away is an ancestor. Also look for merges
549 661 # that might be turned into new heads by the strip.
550 662 while heads:
551 663 h = heads.pop()
552 664 n = h
553 665 while True:
554 666 seen[n] = 1
555 667 pp = chlog.parents(n)
556 668 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
557 669 if pp[1] not in seen:
558 670 heads.append(pp[1])
559 671 if pp[0] == revlog.nullid:
560 672 break
561 673 if chlog.rev(pp[0]) < revnum:
562 674 break
563 675 n = pp[0]
564 676 if n == rev:
565 677 break
566 678 r = chlog.reachable(h, rev)
567 679 if rev not in r:
568 680 saveheads.append(h)
569 681 for x in r:
570 682 if chlog.rev(x) > revnum:
571 683 savebases[x] = 1
572 684
573 685 # create a changegroup for all the branches we need to keep
574 if backup is "all":
686 if backup == "all":
575 687 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
576 688 bundle(backupch)
577 689 if saveheads:
578 690 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
579 691 chgrpfile = bundle(backupch)
580 692
581 693 stripall(rev, revnum)
582 694
583 695 change = chlog.read(rev)
584 696 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
585 697 chlog.strip(revnum, revnum)
586 698 if saveheads:
587 699 self.ui.status("adding branch\n")
588 700 commands.unbundle(self.ui, repo, chgrpfile, update=False)
589 if backup is not "strip":
701 if backup != "strip":
590 702 os.unlink(chgrpfile)
591 703
592 704 def isapplied(self, patch):
593 705 """returns (index, rev, patch)"""
594 706 for i in xrange(len(self.applied)):
595 p = self.applied[i]
596 a = p.split(':')
597 if a[1] == patch:
598 return (i, a[0], a[1])
707 a = self.applied[i]
708 if a.name == patch:
709 return (i, a.rev, a.name)
599 710 return None
600 711
601 def lookup(self, patch):
712 # if the exact patch name does not exist, we try a few
713 # variations. If strict is passed, we try only #1
714 #
715 # 1) a number to indicate an offset in the series file
716 # 2) a unique substring of the patch name was given
717 # 3) patchname[-+]num to indicate an offset in the series file
718 def lookup(self, patch, strict=False):
719 patch = patch and str(patch)
720
721 def partial_name(s):
722 if s in self.series:
723 return s
724 matches = [x for x in self.series if s in x]
725 if len(matches) > 1:
726 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
727 for m in matches:
728 self.ui.warn(' %s\n' % m)
729 return None
730 if matches:
731 return matches[0]
732 if len(self.series) > 0 and len(self.applied) > 0:
733 if s == 'qtip':
734 return self.series[self.series_end()-1]
735 if s == 'qbase':
736 return self.series[0]
737 return None
602 738 if patch == None:
603 739 return None
604 if patch in self.series:
605 return patch
606 if not os.path.isfile(os.path.join(self.path, patch)):
740
741 # we don't want to return a partial match until we make
742 # sure the file name passed in does not exist (checked below)
743 res = partial_name(patch)
744 if res and res == patch:
745 return res
746
747 if not os.path.isfile(self.join(patch)):
607 748 try:
608 749 sno = int(patch)
609 750 except(ValueError, OverflowError):
610 self.ui.warn("patch %s not in series\n" % patch)
611 sys.exit(1)
612 if sno >= len(self.series):
613 self.ui.warn("patch number %d is out of range\n" % sno)
614 sys.exit(1)
615 patch = self.series[sno]
616 else:
617 self.ui.warn("patch %s not in series\n" % patch)
618 sys.exit(1)
619 return patch
751 pass
752 else:
753 if sno < len(self.series):
754 return self.series[sno]
755 if not strict:
756 # return any partial match made above
757 if res:
758 return res
759 minus = patch.rsplit('-', 1)
760 if len(minus) > 1:
761 res = partial_name(minus[0])
762 if res:
763 i = self.series.index(res)
764 try:
765 off = int(minus[1] or 1)
766 except(ValueError, OverflowError):
767 pass
768 else:
769 if i - off >= 0:
770 return self.series[i - off]
771 plus = patch.rsplit('+', 1)
772 if len(plus) > 1:
773 res = partial_name(plus[0])
774 if res:
775 i = self.series.index(res)
776 try:
777 off = int(plus[1] or 1)
778 except(ValueError, OverflowError):
779 pass
780 else:
781 if i + off < len(self.series):
782 return self.series[i + off]
783 raise util.Abort(_("patch %s not in series") % patch)
620 784
621 785 def push(self, repo, patch=None, force=False, list=False,
622 786 mergeq=None, wlock=None):
623 787 if not wlock:
624 788 wlock = repo.wlock()
625 789 patch = self.lookup(patch)
626 790 if patch and self.isapplied(patch):
627 self.ui.warn("patch %s is already applied\n" % patch)
791 self.ui.warn(_("patch %s is already applied\n") % patch)
628 792 sys.exit(1)
629 793 if self.series_end() == len(self.series):
630 self.ui.warn("File series fully applied\n")
794 self.ui.warn(_("patch series fully applied\n"))
631 795 sys.exit(1)
632 796 if not force:
633 797 self.check_localchanges(repo)
634 798
635 799 self.applied_dirty = 1;
636 800 start = self.series_end()
637 801 if start > 0:
638 802 self.check_toppatch(repo)
639 803 if not patch:
640 804 patch = self.series[start]
641 805 end = start + 1
642 806 else:
643 807 end = self.series.index(patch, start) + 1
644 808 s = self.series[start:end]
645 809 if mergeq:
646 810 ret = self.mergepatch(repo, mergeq, s, wlock)
647 811 else:
648 812 ret = self.apply(repo, s, list, wlock=wlock)
649 top = self.applied[-1].split(':')[1]
813 top = self.applied[-1].name
650 814 if ret[0]:
651 815 self.ui.write("Errors during apply, please fix and refresh %s\n" %
652 816 top)
653 817 else:
654 818 self.ui.write("Now at: %s\n" % top)
655 819 return ret[0]
656 820
657 def pop(self, repo, patch=None, force=False, update=True, wlock=None):
821 def pop(self, repo, patch=None, force=False, update=True, all=False,
822 wlock=None):
658 823 def getfile(f, rev):
659 824 t = repo.file(f).read(rev)
660 825 try:
661 826 repo.wfile(f, "w").write(t)
662 827 except IOError:
663 828 try:
664 829 os.makedirs(os.path.dirname(repo.wjoin(f)))
665 830 except OSError, err:
666 831 if err.errno != errno.EEXIST: raise
667 832 repo.wfile(f, "w").write(t)
668 833
669 834 if not wlock:
670 835 wlock = repo.wlock()
671 836 if patch:
672 837 # index, rev, patch
673 838 info = self.isapplied(patch)
674 839 if not info:
675 840 patch = self.lookup(patch)
676 841 info = self.isapplied(patch)
677 842 if not info:
678 self.ui.warn("patch %s is not applied\n" % patch)
679 sys.exit(1)
843 raise util.Abort(_("patch %s is not applied") % patch)
680 844 if len(self.applied) == 0:
681 self.ui.warn("No patches applied\n")
845 self.ui.warn(_("no patches applied\n"))
682 846 sys.exit(1)
683 847
684 848 if not update:
685 849 parents = repo.dirstate.parents()
686 rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
850 rr = [ revlog.bin(x.rev) for x in self.applied ]
687 851 for p in parents:
688 852 if p in rr:
689 853 self.ui.warn("qpop: forcing dirstate update\n")
690 854 update = True
691 855
692 856 if not force and update:
693 857 self.check_localchanges(repo)
694 858
695 859 self.applied_dirty = 1;
696 860 end = len(self.applied)
697 861 if not patch:
698 info = [len(self.applied) - 1] + self.applied[-1].split(':')
862 if all:
863 popi = 0
864 else:
865 popi = len(self.applied) - 1
866 else:
867 popi = info[0] + 1
868 if popi >= end:
869 self.ui.warn("qpop: %s is already at the top\n" % patch)
870 return
871 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
872
699 873 start = info[0]
700 874 rev = revlog.bin(info[1])
701 875
702 876 # we know there are no local changes, so we can make a simplified
703 877 # form of hg.update.
704 878 if update:
705 879 top = self.check_toppatch(repo)
706 880 qp = self.qparents(repo, rev)
707 881 changes = repo.changelog.read(qp)
708 mf1 = repo.manifest.readflags(changes[0])
709 882 mmap = repo.manifest.read(changes[0])
710 (c, a, r, d, u) = repo.changes(qp, top)
883 m, a, r, d, u = repo.status(qp, top)[:5]
711 884 if d:
712 885 raise util.Abort("deletions found between repo revs")
713 for f in c:
886 for f in m:
714 887 getfile(f, mmap[f])
715 888 for f in r:
716 889 getfile(f, mmap[f])
717 util.set_exec(repo.wjoin(f), mf1[f])
718 repo.dirstate.update(c + r, 'n')
890 util.set_exec(repo.wjoin(f), mmap.execf(f))
891 repo.dirstate.update(m + r, 'n')
719 892 for f in a:
720 893 try: os.unlink(repo.wjoin(f))
721 894 except: raise
722 895 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
723 896 except: pass
724 897 if a:
725 898 repo.dirstate.forget(a)
726 899 repo.dirstate.setparents(qp, revlog.nullid)
727 900 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
728 901 del self.applied[start:end]
729 902 if len(self.applied):
730 self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
903 self.ui.write("Now at: %s\n" % self.applied[-1].name)
731 904 else:
732 905 self.ui.write("Patch queue now empty\n")
733 906
734 def diff(self, repo, files):
907 def diff(self, repo, pats, opts):
735 908 top = self.check_toppatch(repo)
736 909 if not top:
737 910 self.ui.write("No patches applied\n")
738 911 return
739 912 qp = self.qparents(repo, top)
740 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
913 self.printdiff(repo, qp, files=pats, opts=opts)
741 914
742 def refresh(self, repo, short=False):
915 def refresh(self, repo, pats=None, **opts):
743 916 if len(self.applied) == 0:
744 917 self.ui.write("No patches applied\n")
745 918 return
746 919 wlock = repo.wlock()
747 920 self.check_toppatch(repo)
748 qp = self.qparents(repo)
749 (top, patch) = self.applied[-1].split(':')
921 (top, patch) = (self.applied[-1].rev, self.applied[-1].name)
750 922 top = revlog.bin(top)
751 923 cparents = repo.changelog.parents(top)
752 924 patchparent = self.qparents(repo, top)
753 925 message, comments, user, date, patchfound = self.readheaders(patch)
754 926
755 927 patchf = self.opener(patch, "w")
928 msg = opts.get('msg', '').rstrip()
929 if msg:
930 if comments:
931 # Remove existing message.
932 ci = 0
933 for mi in range(len(message)):
934 while message[mi] != comments[ci]:
935 ci += 1
936 del comments[ci]
937 comments.append(msg)
756 938 if comments:
757 939 comments = "\n".join(comments) + '\n\n'
758 940 patchf.write(comments)
759 941
942 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
760 943 tip = repo.changelog.tip()
761 944 if top == tip:
762 945 # if the top of our patch queue is also the tip, there is an
763 946 # optimization here. We update the dirstate in place and strip
764 947 # off the tip commit. Then just commit the current directory
765 948 # tree. We can also send repo.commit the list of files
766 949 # changed to speed up the diff
767 950 #
768 951 # in short mode, we only diff the files included in the
769 952 # patch already
770 953 #
771 954 # this should really read:
772 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
955 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
773 956 # but we do it backwards to take advantage of manifest/chlog
774 # caching against the next repo.changes call
957 # caching against the next repo.status call
775 958 #
776 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
777 if short:
778 filelist = cc + aa + dd
959 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
960 if opts.get('short'):
961 filelist = mm + aa + dd
779 962 else:
780 963 filelist = None
781 (c, a, r, d, u) = repo.changes(None, None, filelist)
964 m, a, r, d, u = repo.status(files=filelist)[:5]
782 965
783 966 # we might end up with files that were added between tip and
784 967 # the dirstate parent, but then changed in the local dirstate.
785 968 # in this case, we want them to only show up in the added section
786 for x in c:
969 for x in m:
787 970 if x not in aa:
788 cc.append(x)
971 mm.append(x)
789 972 # we might end up with files added by the local dirstate that
790 973 # were deleted by the patch. In this case, they should only
791 974 # show up in the changed section.
792 975 for x in a:
793 976 if x in dd:
794 977 del dd[dd.index(x)]
795 cc.append(x)
978 mm.append(x)
796 979 else:
797 980 aa.append(x)
798 981 # make sure any files deleted in the local dirstate
799 982 # are not in the add or change column of the patch
800 983 forget = []
801 984 for x in d + r:
802 985 if x in aa:
803 986 del aa[aa.index(x)]
804 987 forget.append(x)
805 988 continue
806 elif x in cc:
807 del cc[cc.index(x)]
989 elif x in mm:
990 del mm[mm.index(x)]
808 991 dd.append(x)
809 992
810 c = list(util.unique(cc))
993 m = list(util.unique(mm))
811 994 r = list(util.unique(dd))
812 995 a = list(util.unique(aa))
813 filelist = list(util.unique(c + r + a ))
814 commands.dodiff(patchf, self.ui, repo, patchparent, None,
815 filelist, changes=(c, a, r, [], u))
996 filelist = filter(matchfn, util.unique(m + r + a))
997 self.printdiff(repo, patchparent, files=filelist,
998 changes=(m, a, r, [], u), fp=patchf)
816 999 patchf.close()
817 1000
818 1001 changes = repo.changelog.read(tip)
819 1002 repo.dirstate.setparents(*cparents)
1003 copies = [(f, repo.dirstate.copied(f)) for f in a]
820 1004 repo.dirstate.update(a, 'a')
1005 for dst, src in copies:
1006 repo.dirstate.copy(src, dst)
821 1007 repo.dirstate.update(r, 'r')
822 repo.dirstate.update(c, 'n')
1008 # if the patch excludes a modified file, mark that file with mtime=0
1009 # so status can see it.
1010 mm = []
1011 for i in range(len(m)-1, -1, -1):
1012 if not matchfn(m[i]):
1013 mm.append(m[i])
1014 del m[i]
1015 repo.dirstate.update(m, 'n')
1016 repo.dirstate.update(mm, 'n', st_mtime=0)
823 1017 repo.dirstate.forget(forget)
824 1018
825 if not message:
826 message = "patch queue: %s\n" % patch
1019 if not msg:
1020 if not message:
1021 message = "patch queue: %s\n" % patch
1022 else:
1023 message = "\n".join(message)
827 1024 else:
828 message = "\n".join(message)
1025 message = msg
1026
829 1027 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
830 1028 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
831 self.applied[-1] = revlog.hex(n) + ':' + patch
1029 self.applied[-1] = statusentry(revlog.hex(n), patch)
832 1030 self.applied_dirty = 1
833 1031 else:
834 commands.dodiff(patchf, self.ui, repo, patchparent, None)
1032 self.printdiff(repo, patchparent, fp=patchf)
835 1033 patchf.close()
836 1034 self.pop(repo, force=True, wlock=wlock)
837 1035 self.push(repo, force=True, wlock=wlock)
838 1036
839 1037 def init(self, repo, create=False):
840 1038 if os.path.isdir(self.path):
841 raise util.Abort("patch queue directory already exists")
1039 raise util.Abort(_("patch queue directory already exists"))
842 1040 os.mkdir(self.path)
843 1041 if create:
844 1042 return self.qrepo(create=True)
845 1043
846 1044 def unapplied(self, repo, patch=None):
847 1045 if patch and patch not in self.series:
848 self.ui.warn("%s not in the series file\n" % patch)
849 sys.exit(1)
1046 raise util.Abort(_("patch %s is not in series file") % patch)
850 1047 if not patch:
851 1048 start = self.series_end()
852 1049 else:
853 1050 start = self.series.index(patch) + 1
854 for p in self.series[start:]:
855 self.ui.write("%s\n" % p)
1051 unapplied = []
1052 for i in xrange(start, len(self.series)):
1053 pushable, reason = self.pushable(i)
1054 if pushable:
1055 unapplied.append((i, self.series[i]))
1056 self.explain_pushable(i)
1057 return unapplied
856 1058
857 def qseries(self, repo, missing=None):
858 start = self.series_end()
1059 def qseries(self, repo, missing=None, summary=False):
1060 start = self.series_end(all_patches=True)
859 1061 if not missing:
860 for p in self.series[:start]:
1062 for i in range(len(self.series)):
1063 patch = self.series[i]
861 1064 if self.ui.verbose:
862 self.ui.write("%d A " % self.series.index(p))
863 self.ui.write("%s\n" % p)
864 for p in self.series[start:]:
865 if self.ui.verbose:
866 self.ui.write("%d U " % self.series.index(p))
867 self.ui.write("%s\n" % p)
1065 if i < start:
1066 status = 'A'
1067 elif self.pushable(i)[0]:
1068 status = 'U'
1069 else:
1070 status = 'G'
1071 self.ui.write('%d %s ' % (i, status))
1072 if summary:
1073 msg = self.readheaders(patch)[0]
1074 msg = msg and ': ' + msg[0] or ': '
1075 else:
1076 msg = ''
1077 self.ui.write('%s%s\n' % (patch, msg))
868 1078 else:
869 list = []
1079 msng_list = []
870 1080 for root, dirs, files in os.walk(self.path):
871 1081 d = root[len(self.path) + 1:]
872 1082 for f in files:
873 1083 fl = os.path.join(d, f)
874 1084 if (fl not in self.series and
875 1085 fl not in (self.status_path, self.series_path)
876 1086 and not fl.startswith('.')):
877 list.append(fl)
878 list.sort()
879 if list:
880 for x in list:
881 if self.ui.verbose:
882 self.ui.write("D ")
883 self.ui.write("%s\n" % x)
1087 msng_list.append(fl)
1088 msng_list.sort()
1089 for x in msng_list:
1090 if self.ui.verbose:
1091 self.ui.write("D ")
1092 self.ui.write("%s\n" % x)
884 1093
885 1094 def issaveline(self, l):
886 name = l.split(':')[1]
887 if name == '.hg.patches.save.line':
1095 if l.name == '.hg.patches.save.line':
888 1096 return True
889 1097
890 1098 def qrepo(self, create=False):
891 if create or os.path.isdir(os.path.join(self.path, ".hg")):
1099 if create or os.path.isdir(self.join(".hg")):
892 1100 return hg.repository(self.ui, path=self.path, create=create)
893 1101
894 1102 def restore(self, repo, rev, delete=None, qupdate=None):
895 1103 c = repo.changelog.read(rev)
896 1104 desc = c[4].strip()
897 1105 lines = desc.splitlines()
898 1106 i = 0
899 1107 datastart = None
900 1108 series = []
901 1109 applied = []
902 1110 qpp = None
903 1111 for i in xrange(0, len(lines)):
904 1112 if lines[i] == 'Patch Data:':
905 1113 datastart = i + 1
906 1114 elif lines[i].startswith('Dirstate:'):
907 1115 l = lines[i].rstrip()
908 1116 l = l[10:].split(' ')
909 1117 qpp = [ hg.bin(x) for x in l ]
910 1118 elif datastart != None:
911 1119 l = lines[i].rstrip()
912 index = l.index(':')
913 id = l[:index]
914 file = l[index + 1:]
915 if id:
916 applied.append(l)
917 series.append(file)
1120 se = statusentry(l)
1121 file_ = se.name
1122 if se.rev:
1123 applied.append(se)
1124 series.append(file_)
918 1125 if datastart == None:
919 1126 self.ui.warn("No saved patch data found\n")
920 1127 return 1
921 1128 self.ui.warn("restoring status: %s\n" % lines[0])
922 1129 self.full_series = series
923 1130 self.applied = applied
924 self.read_series(self.full_series)
1131 self.parse_series()
925 1132 self.series_dirty = 1
926 1133 self.applied_dirty = 1
927 1134 heads = repo.changelog.heads()
928 1135 if delete:
929 1136 if rev not in heads:
930 1137 self.ui.warn("save entry has children, leaving it alone\n")
931 1138 else:
932 1139 self.ui.warn("removing save entry %s\n" % hg.short(rev))
933 1140 pp = repo.dirstate.parents()
934 1141 if rev in pp:
935 1142 update = True
936 1143 else:
937 1144 update = False
938 1145 self.strip(repo, rev, update=update, backup='strip')
939 1146 if qpp:
940 1147 self.ui.warn("saved queue repository parents: %s %s\n" %
941 1148 (hg.short(qpp[0]), hg.short(qpp[1])))
942 1149 if qupdate:
943 1150 print "queue directory updating"
944 1151 r = self.qrepo()
945 1152 if not r:
946 1153 self.ui.warn("Unable to load queue repository\n")
947 1154 return 1
948 r.update(qpp[0], allow=False, force=True)
1155 hg.clean(r, qpp[0])
949 1156
950 1157 def save(self, repo, msg=None):
951 1158 if len(self.applied) == 0:
952 1159 self.ui.warn("save: no patches applied, exiting\n")
953 1160 return 1
954 1161 if self.issaveline(self.applied[-1]):
955 1162 self.ui.warn("status is already saved\n")
956 1163 return 1
957 1164
958 1165 ar = [ ':' + x for x in self.full_series ]
959 1166 if not msg:
960 1167 msg = "hg patches saved state"
961 1168 else:
962 1169 msg = "hg patches: " + msg.rstrip('\r\n')
963 1170 r = self.qrepo()
964 1171 if r:
965 1172 pp = r.dirstate.parents()
966 1173 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
967 1174 msg += "\n\nPatch Data:\n"
968 text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar)
969 + '\n' or "")
1175 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1176 "\n".join(ar) + '\n' or "")
970 1177 n = repo.commit(None, text, user=None, force=1)
971 1178 if not n:
972 1179 self.ui.warn("repo commit failed\n")
973 1180 return 1
974 self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
1181 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
975 1182 self.applied_dirty = 1
976 1183
977 def series_end(self):
1184 def full_series_end(self):
1185 if len(self.applied) > 0:
1186 p = self.applied[-1].name
1187 end = self.find_series(p)
1188 if end == None:
1189 return len(self.full_series)
1190 return end + 1
1191 return 0
1192
1193 def series_end(self, all_patches=False):
978 1194 end = 0
1195 def next(start):
1196 if all_patches:
1197 return start
1198 i = start
1199 while i < len(self.series):
1200 p, reason = self.pushable(i)
1201 if p:
1202 break
1203 self.explain_pushable(i)
1204 i += 1
1205 return i
979 1206 if len(self.applied) > 0:
980 (top, p) = self.applied[-1].split(':')
1207 p = self.applied[-1].name
981 1208 try:
982 1209 end = self.series.index(p)
983 1210 except ValueError:
984 1211 return 0
985 return end + 1
986 return end
1212 return next(end + 1)
1213 return next(end)
987 1214
988 1215 def qapplied(self, repo, patch=None):
989 1216 if patch and patch not in self.series:
990 self.ui.warn("%s not in the series file\n" % patch)
991 sys.exit(1)
1217 raise util.Abort(_("patch %s is not in series file") % patch)
992 1218 if not patch:
993 1219 end = len(self.applied)
994 1220 else:
995 1221 end = self.series.index(patch) + 1
996 1222 for x in xrange(end):
997 1223 p = self.appliedname(x)
998 1224 self.ui.write("%s\n" % p)
999 1225
1000 1226 def appliedname(self, index):
1001 p = self.applied[index]
1227 pname = self.applied[index].name
1002 1228 if not self.ui.verbose:
1003 p = p.split(':')[1]
1229 p = pname
1230 else:
1231 p = str(self.series.index(pname)) + " " + pname
1004 1232 return p
1005 1233
1006 1234 def top(self, repo):
1007 1235 if len(self.applied):
1008 1236 p = self.appliedname(-1)
1009 1237 self.ui.write(p + '\n')
1010 1238 else:
1011 1239 self.ui.write("No patches applied\n")
1012 1240
1013 1241 def next(self, repo):
1014 1242 end = self.series_end()
1015 1243 if end == len(self.series):
1016 1244 self.ui.write("All patches applied\n")
1017 1245 else:
1018 self.ui.write(self.series[end] + '\n')
1246 p = self.series[end]
1247 if self.ui.verbose:
1248 self.ui.write("%d " % self.series.index(p))
1249 self.ui.write(p + '\n')
1019 1250
1020 1251 def prev(self, repo):
1021 1252 if len(self.applied) > 1:
1022 1253 p = self.appliedname(-2)
1023 1254 self.ui.write(p + '\n')
1024 1255 elif len(self.applied) == 1:
1025 1256 self.ui.write("Only one patch applied\n")
1026 1257 else:
1027 1258 self.ui.write("No patches applied\n")
1028 1259
1029 1260 def qimport(self, repo, files, patch=None, existing=None, force=None):
1030 1261 if len(files) > 1 and patch:
1031 self.ui.warn("-n option not valid when importing multiple files\n")
1032 sys.exit(1)
1262 raise util.Abort(_('option "-n" not valid when importing multiple '
1263 'files'))
1033 1264 i = 0
1034 1265 added = []
1035 1266 for filename in files:
1036 1267 if existing:
1037 1268 if not patch:
1038 1269 patch = filename
1039 if not os.path.isfile(os.path.join(self.path, patch)):
1040 self.ui.warn("patch %s does not exist\n" % patch)
1041 sys.exit(1)
1270 if not os.path.isfile(self.join(patch)):
1271 raise util.Abort(_("patch %s does not exist") % patch)
1042 1272 else:
1043 1273 try:
1044 1274 text = file(filename).read()
1045 1275 except IOError:
1046 self.ui.warn("Unable to read %s\n" % patch)
1047 sys.exit(1)
1276 raise util.Abort(_("unable to read %s") % patch)
1048 1277 if not patch:
1049 1278 patch = os.path.split(filename)[1]
1050 if not force and os.path.isfile(os.path.join(self.path, patch)):
1051 self.ui.warn("patch %s already exists\n" % patch)
1052 sys.exit(1)
1279 if not force and os.path.exists(self.join(patch)):
1280 raise util.Abort(_('patch "%s" already exists') % patch)
1053 1281 patchf = self.opener(patch, "w")
1054 1282 patchf.write(text)
1055 1283 if patch in self.series:
1056 self.ui.warn("patch %s is already in the series file\n" % patch)
1057 sys.exit(1)
1058 index = self.series_end() + i
1284 raise util.Abort(_('patch %s is already in the series file')
1285 % patch)
1286 index = self.full_series_end() + i
1059 1287 self.full_series[index:index] = [patch]
1060 self.read_series(self.full_series)
1288 self.parse_series()
1061 1289 self.ui.warn("adding %s to series file\n" % patch)
1062 1290 i += 1
1063 1291 added.append(patch)
1064 1292 patch = None
1065 1293 self.series_dirty = 1
1066 1294 qrepo = self.qrepo()
1067 1295 if qrepo:
1068 1296 qrepo.add(added)
1069 1297
1070 def delete(ui, repo, patch, **opts):
1071 """remove a patch from the series file"""
1072 q = repomap[repo]
1073 q.delete(repo, patch)
1298 def delete(ui, repo, patch, *patches, **opts):
1299 """remove patches from queue
1300
1301 The patches must not be applied.
1302 With -k, the patch files are preserved in the patch directory."""
1303 q = repo.mq
1304 q.delete(repo, (patch,) + patches, keep=opts.get('keep'))
1074 1305 q.save_dirty()
1075 1306 return 0
1076 1307
1077 1308 def applied(ui, repo, patch=None, **opts):
1078 1309 """print the patches already applied"""
1079 repomap[repo].qapplied(repo, patch)
1310 repo.mq.qapplied(repo, patch)
1080 1311 return 0
1081 1312
1082 1313 def unapplied(ui, repo, patch=None, **opts):
1083 1314 """print the patches not yet applied"""
1084 repomap[repo].unapplied(repo, patch)
1085 return 0
1315 for i, p in repo.mq.unapplied(repo, patch):
1316 if ui.verbose:
1317 ui.write("%d " % i)
1318 ui.write("%s\n" % p)
1086 1319
1087 1320 def qimport(ui, repo, *filename, **opts):
1088 1321 """import a patch"""
1089 q = repomap[repo]
1322 q = repo.mq
1090 1323 q.qimport(repo, filename, patch=opts['name'],
1091 1324 existing=opts['existing'], force=opts['force'])
1092 1325 q.save_dirty()
1093 1326 return 0
1094 1327
1095 1328 def init(ui, repo, **opts):
1096 """init a new queue repository"""
1097 q = repomap[repo]
1329 """init a new queue repository
1330
1331 The queue repository is unversioned by default. If -c is
1332 specified, qinit will create a separate nested repository
1333 for patches. Use qcommit to commit changes to this queue
1334 repository."""
1335 q = repo.mq
1098 1336 r = q.init(repo, create=opts['create_repo'])
1099 1337 q.save_dirty()
1100 1338 if r:
1101 1339 fp = r.wopener('.hgignore', 'w')
1102 1340 print >> fp, 'syntax: glob'
1103 1341 print >> fp, 'status'
1104 1342 fp.close()
1105 1343 r.wopener('series', 'w').close()
1106 1344 r.add(['.hgignore', 'series'])
1107 1345 return 0
1108 1346
1347 def clone(ui, source, dest=None, **opts):
1348 '''clone main and patch repository at same time
1349
1350 If source is local, destination will have no patches applied. If
1351 source is remote, this command can not check if patches are
1352 applied in source, so cannot guarantee that patches are not
1353 applied in destination. If you clone remote repository, be sure
1354 before that it has no patches applied.
1355
1356 Source patch repository is looked for in <src>/.hg/patches by
1357 default. Use -p <url> to change.
1358 '''
1359 commands.setremoteconfig(ui, opts)
1360 if dest is None:
1361 dest = hg.defaultdest(source)
1362 sr = hg.repository(ui, ui.expandpath(source))
1363 qbase, destrev = None, None
1364 if sr.local():
1365 reposetup(ui, sr)
1366 if sr.mq.applied:
1367 qbase = revlog.bin(sr.mq.applied[0].rev)
1368 if not hg.islocal(dest):
1369 destrev = sr.parents(qbase)[0]
1370 ui.note(_('cloning main repo\n'))
1371 sr, dr = hg.clone(ui, sr, dest,
1372 pull=opts['pull'],
1373 rev=destrev,
1374 update=False,
1375 stream=opts['uncompressed'])
1376 ui.note(_('cloning patch repo\n'))
1377 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1378 dr.url() + '/.hg/patches',
1379 pull=opts['pull'],
1380 update=not opts['noupdate'],
1381 stream=opts['uncompressed'])
1382 if dr.local():
1383 if qbase:
1384 ui.note(_('stripping applied patches from destination repo\n'))
1385 reposetup(ui, dr)
1386 dr.mq.strip(dr, qbase, update=False, backup=None)
1387 if not opts['noupdate']:
1388 ui.note(_('updating destination repo\n'))
1389 hg.update(dr, dr.changelog.tip())
1390
1109 1391 def commit(ui, repo, *pats, **opts):
1110 1392 """commit changes in the queue repository"""
1111 q = repomap[repo]
1393 q = repo.mq
1112 1394 r = q.qrepo()
1113 1395 if not r: raise util.Abort('no queue repository')
1114 1396 commands.commit(r.ui, r, *pats, **opts)
1115 1397
1116 1398 def series(ui, repo, **opts):
1117 1399 """print the entire series file"""
1118 repomap[repo].qseries(repo, missing=opts['missing'])
1400 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1119 1401 return 0
1120 1402
1121 1403 def top(ui, repo, **opts):
1122 1404 """print the name of the current patch"""
1123 repomap[repo].top(repo)
1405 repo.mq.top(repo)
1124 1406 return 0
1125 1407
1126 1408 def next(ui, repo, **opts):
1127 1409 """print the name of the next patch"""
1128 repomap[repo].next(repo)
1410 repo.mq.next(repo)
1129 1411 return 0
1130 1412
1131 1413 def prev(ui, repo, **opts):
1132 1414 """print the name of the previous patch"""
1133 repomap[repo].prev(repo)
1415 repo.mq.prev(repo)
1134 1416 return 0
1135 1417
1136 1418 def new(ui, repo, patch, **opts):
1137 """create a new patch"""
1138 q = repomap[repo]
1139 q.new(repo, patch, msg=opts['message'], force=opts['force'])
1419 """create a new patch
1420
1421 qnew creates a new patch on top of the currently-applied patch
1422 (if any). It will refuse to run if there are any outstanding
1423 changes unless -f is specified, in which case the patch will
1424 be initialised with them.
1425
1426 -e, -m or -l set the patch header as well as the commit message.
1427 If none is specified, the patch header is empty and the
1428 commit message is 'New patch: PATCH'"""
1429 q = repo.mq
1430 message = commands.logmessage(opts)
1431 if opts['edit']:
1432 message = ui.edit(message, ui.username())
1433 q.new(repo, patch, msg=message, force=opts['force'])
1434 q.save_dirty()
1435 return 0
1436
1437 def refresh(ui, repo, *pats, **opts):
1438 """update the current patch
1439
1440 If any file patterns are provided, the refreshed patch will contain only
1441 the modifications that match those patterns; the remaining modifications
1442 will remain in the working directory.
1443 """
1444 q = repo.mq
1445 message = commands.logmessage(opts)
1446 if opts['edit']:
1447 if message:
1448 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1449 patch = q.applied[-1].name
1450 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1451 message = ui.edit('\n'.join(message), user or ui.username())
1452 q.refresh(repo, pats, msg=message, **opts)
1140 1453 q.save_dirty()
1141 1454 return 0
1142 1455
1143 def refresh(ui, repo, **opts):
1144 """update the current patch"""
1145 q = repomap[repo]
1146 q.refresh(repo, short=opts['short'])
1147 q.save_dirty()
1456 def diff(ui, repo, *pats, **opts):
1457 """diff of the current patch"""
1458 repo.mq.diff(repo, pats, opts)
1148 1459 return 0
1149 1460
1150 def diff(ui, repo, *files, **opts):
1151 """diff of the current patch"""
1152 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1153 repomap[repo].diff(repo, list(files))
1154 return 0
1461 def fold(ui, repo, *files, **opts):
1462 """fold the named patches into the current patch
1463
1464 Patches must not yet be applied. Each patch will be successively
1465 applied to the current patch in the order given. If all the
1466 patches apply successfully, the current patch will be refreshed
1467 with the new cumulative patch, and the folded patches will
1468 be deleted. With -k/--keep, the folded patch files will not
1469 be removed afterwards.
1470
1471 The header for each folded patch will be concatenated with
1472 the current patch header, separated by a line of '* * *'."""
1473
1474 q = repo.mq
1475
1476 if not files:
1477 raise util.Abort(_('qfold requires at least one patch name'))
1478 if not q.check_toppatch(repo):
1479 raise util.Abort(_('No patches applied\n'))
1480
1481 message = commands.logmessage(opts)
1482 if opts['edit']:
1483 if message:
1484 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1485
1486 parent = q.lookup('qtip')
1487 patches = []
1488 messages = []
1489 for f in files:
1490 p = q.lookup(f)
1491 if p in patches or p == parent:
1492 ui.warn(_('Skipping already folded patch %s') % p)
1493 if q.isapplied(p):
1494 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1495 patches.append(p)
1496
1497 for p in patches:
1498 if not message:
1499 messages.append(q.readheaders(p)[0])
1500 pf = q.join(p)
1501 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1502 if not patchsuccess:
1503 raise util.Abort(_('Error folding patch %s') % p)
1504 patch.updatedir(ui, repo, files)
1505
1506 if not message:
1507 message, comments, user = q.readheaders(parent)[0:3]
1508 for msg in messages:
1509 message.append('* * *')
1510 message.extend(msg)
1511 message = '\n'.join(message)
1512
1513 if opts['edit']:
1514 message = ui.edit(message, user or ui.username())
1515
1516 q.refresh(repo, msg=message)
1517 q.delete(repo, patches, keep=opts['keep'])
1518 q.save_dirty()
1519
1520 def guard(ui, repo, *args, **opts):
1521 '''set or print guards for a patch
1522
1523 Guards control whether a patch can be pushed. A patch with no
1524 guards is always pushed. A patch with a positive guard ("+foo") is
1525 pushed only if the qselect command has activated it. A patch with
1526 a negative guard ("-foo") is never pushed if the qselect command
1527 has activated it.
1528
1529 With no arguments, print the currently active guards.
1530 With arguments, set guards for the named patch.
1531
1532 To set a negative guard "-foo" on topmost patch ("--" is needed so
1533 hg will not interpret "-foo" as an option):
1534 hg qguard -- -foo
1535
1536 To set guards on another patch:
1537 hg qguard other.patch +2.6.17 -stable
1538 '''
1539 def status(idx):
1540 guards = q.series_guards[idx] or ['unguarded']
1541 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1542 q = repo.mq
1543 patch = None
1544 args = list(args)
1545 if opts['list']:
1546 if args or opts['none']:
1547 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1548 for i in xrange(len(q.series)):
1549 status(i)
1550 return
1551 if not args or args[0][0:1] in '-+':
1552 if not q.applied:
1553 raise util.Abort(_('no patches applied'))
1554 patch = q.applied[-1].name
1555 if patch is None and args[0][0:1] not in '-+':
1556 patch = args.pop(0)
1557 if patch is None:
1558 raise util.Abort(_('no patch to work with'))
1559 if args or opts['none']:
1560 q.set_guards(q.find_series(patch), args)
1561 q.save_dirty()
1562 else:
1563 status(q.series.index(q.lookup(patch)))
1564
1565 def header(ui, repo, patch=None):
1566 """Print the header of the topmost or specified patch"""
1567 q = repo.mq
1568
1569 if patch:
1570 patch = q.lookup(patch)
1571 else:
1572 if not q.applied:
1573 ui.write('No patches applied\n')
1574 return
1575 patch = q.lookup('qtip')
1576 message = repo.mq.readheaders(patch)[0]
1577
1578 ui.write('\n'.join(message) + '\n')
1155 1579
1156 1580 def lastsavename(path):
1157 (dir, base) = os.path.split(path)
1158 names = os.listdir(dir)
1581 (directory, base) = os.path.split(path)
1582 names = os.listdir(directory)
1159 1583 namere = re.compile("%s.([0-9]+)" % base)
1160 max = None
1584 maxindex = None
1161 1585 maxname = None
1162 1586 for f in names:
1163 1587 m = namere.match(f)
1164 1588 if m:
1165 1589 index = int(m.group(1))
1166 if max == None or index > max:
1167 max = index
1590 if maxindex == None or index > maxindex:
1591 maxindex = index
1168 1592 maxname = f
1169 1593 if maxname:
1170 return (os.path.join(dir, maxname), max)
1594 return (os.path.join(directory, maxname), maxindex)
1171 1595 return (None, None)
1172 1596
1173 1597 def savename(path):
1174 1598 (last, index) = lastsavename(path)
1175 1599 if last is None:
1176 1600 index = 0
1177 1601 newpath = path + ".%d" % (index + 1)
1178 1602 return newpath
1179 1603
1180 1604 def push(ui, repo, patch=None, **opts):
1181 1605 """push the next patch onto the stack"""
1182 q = repomap[repo]
1606 q = repo.mq
1183 1607 mergeq = None
1184 1608
1185 1609 if opts['all']:
1186 1610 patch = q.series[-1]
1187 1611 if opts['merge']:
1188 1612 if opts['name']:
1189 1613 newpath = opts['name']
1190 1614 else:
1191 1615 newpath, i = lastsavename(q.path)
1192 1616 if not newpath:
1193 1617 ui.warn("no saved queues found, please use -n\n")
1194 1618 return 1
1195 1619 mergeq = queue(ui, repo.join(""), newpath)
1196 1620 ui.warn("merging with queue at: %s\n" % mergeq.path)
1197 1621 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1198 1622 mergeq=mergeq)
1199 1623 q.save_dirty()
1200 1624 return ret
1201 1625
1202 1626 def pop(ui, repo, patch=None, **opts):
1203 1627 """pop the current patch off the stack"""
1204 1628 localupdate = True
1205 1629 if opts['name']:
1206 1630 q = queue(ui, repo.join(""), repo.join(opts['name']))
1207 1631 ui.warn('using patch queue: %s\n' % q.path)
1208 1632 localupdate = False
1209 1633 else:
1210 q = repomap[repo]
1211 if opts['all'] and len(q.applied) > 0:
1212 patch = q.applied[0].split(':')[1]
1213 q.pop(repo, patch, force=opts['force'], update=localupdate)
1634 q = repo.mq
1635 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1214 1636 q.save_dirty()
1215 1637 return 0
1216 1638
1639 def rename(ui, repo, patch, name=None, **opts):
1640 """rename a patch
1641
1642 With one argument, renames the current patch to PATCH1.
1643 With two arguments, renames PATCH1 to PATCH2."""
1644
1645 q = repo.mq
1646
1647 if not name:
1648 name = patch
1649 patch = None
1650
1651 if name in q.series:
1652 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1653
1654 absdest = q.join(name)
1655 if os.path.exists(absdest):
1656 raise util.Abort(_('%s already exists') % absdest)
1657
1658 if patch:
1659 patch = q.lookup(patch)
1660 else:
1661 if not q.applied:
1662 ui.write(_('No patches applied\n'))
1663 return
1664 patch = q.lookup('qtip')
1665
1666 if ui.verbose:
1667 ui.write('Renaming %s to %s\n' % (patch, name))
1668 i = q.find_series(patch)
1669 q.full_series[i] = name
1670 q.parse_series()
1671 q.series_dirty = 1
1672
1673 info = q.isapplied(patch)
1674 if info:
1675 q.applied[info[0]] = statusentry(info[1], name)
1676 q.applied_dirty = 1
1677
1678 util.rename(q.join(patch), absdest)
1679 r = q.qrepo()
1680 if r:
1681 wlock = r.wlock()
1682 if r.dirstate.state(name) == 'r':
1683 r.undelete([name], wlock)
1684 r.copy(patch, name, wlock)
1685 r.remove([patch], False, wlock)
1686
1687 q.save_dirty()
1688
1217 1689 def restore(ui, repo, rev, **opts):
1218 1690 """restore the queue state saved by a rev"""
1219 1691 rev = repo.lookup(rev)
1220 q = repomap[repo]
1692 q = repo.mq
1221 1693 q.restore(repo, rev, delete=opts['delete'],
1222 1694 qupdate=opts['update'])
1223 1695 q.save_dirty()
1224 1696 return 0
1225 1697
1226 1698 def save(ui, repo, **opts):
1227 1699 """save current queue state"""
1228 q = repomap[repo]
1229 ret = q.save(repo, msg=opts['message'])
1700 q = repo.mq
1701 message = commands.logmessage(opts)
1702 ret = q.save(repo, msg=message)
1230 1703 if ret:
1231 1704 return ret
1232 1705 q.save_dirty()
1233 1706 if opts['copy']:
1234 1707 path = q.path
1235 1708 if opts['name']:
1236 1709 newpath = os.path.join(q.basepath, opts['name'])
1237 1710 if os.path.exists(newpath):
1238 1711 if not os.path.isdir(newpath):
1239 ui.warn("destination %s exists and is not a directory\n" %
1240 newpath)
1241 sys.exit(1)
1712 raise util.Abort(_('destination %s exists and is not '
1713 'a directory') % newpath)
1242 1714 if not opts['force']:
1243 ui.warn("destination %s exists, use -f to force\n" %
1244 newpath)
1245 sys.exit(1)
1715 raise util.Abort(_('destination %s exists, '
1716 'use -f to force') % newpath)
1246 1717 else:
1247 1718 newpath = savename(path)
1248 1719 ui.warn("copy %s to %s\n" % (path, newpath))
1249 1720 util.copyfiles(path, newpath)
1250 1721 if opts['empty']:
1251 1722 try:
1252 os.unlink(os.path.join(q.path, q.status_path))
1723 os.unlink(q.join(q.status_path))
1253 1724 except:
1254 1725 pass
1255 1726 return 0
1256 1727
1257 1728 def strip(ui, repo, rev, **opts):
1258 1729 """strip a revision and all later revs on the same branch"""
1259 1730 rev = repo.lookup(rev)
1260 1731 backup = 'all'
1261 1732 if opts['backup']:
1262 1733 backup = 'strip'
1263 1734 elif opts['nobackup']:
1264 1735 backup = 'none'
1265 repomap[repo].strip(repo, rev, backup=backup)
1736 repo.mq.strip(repo, rev, backup=backup)
1266 1737 return 0
1267 1738
1268 def version(ui, q=None):
1269 """print the version number"""
1270 ui.write("mq version %s\n" % versionstr)
1271 return 0
1739 def select(ui, repo, *args, **opts):
1740 '''set or print guarded patches to push
1741
1742 Use the qguard command to set or print guards on patch, then use
1743 qselect to tell mq which guards to use. A patch will be pushed if it
1744 has no guards or any positive guards match the currently selected guard,
1745 but will not be pushed if any negative guards match the current guard.
1746 For example:
1747
1748 qguard foo.patch -stable (negative guard)
1749 qguard bar.patch +stable (positive guard)
1750 qselect stable
1751
1752 This activates the "stable" guard. mq will skip foo.patch (because
1753 it has a negative match) but push bar.patch (because it
1754 has a positive match).
1755
1756 With no arguments, prints the currently active guards.
1757 With one argument, sets the active guard.
1758
1759 Use -n/--none to deactivate guards (no other arguments needed).
1760 When no guards are active, patches with positive guards are skipped
1761 and patches with negative guards are pushed.
1762
1763 qselect can change the guards on applied patches. It does not pop
1764 guarded patches by default. Use --pop to pop back to the last applied
1765 patch that is not guarded. Use --reapply (which implies --pop) to push
1766 back to the current patch afterwards, but skip guarded patches.
1767
1768 Use -s/--series to print a list of all guards in the series file (no
1769 other arguments needed). Use -v for more information.'''
1770
1771 q = repo.mq
1772 guards = q.active()
1773 if args or opts['none']:
1774 old_unapplied = q.unapplied(repo)
1775 old_guarded = [i for i in xrange(len(q.applied)) if
1776 not q.pushable(i)[0]]
1777 q.set_active(args)
1778 q.save_dirty()
1779 if not args:
1780 ui.status(_('guards deactivated\n'))
1781 if not opts['pop'] and not opts['reapply']:
1782 unapplied = q.unapplied(repo)
1783 guarded = [i for i in xrange(len(q.applied))
1784 if not q.pushable(i)[0]]
1785 if len(unapplied) != len(old_unapplied):
1786 ui.status(_('number of unguarded, unapplied patches has '
1787 'changed from %d to %d\n') %
1788 (len(old_unapplied), len(unapplied)))
1789 if len(guarded) != len(old_guarded):
1790 ui.status(_('number of guarded, applied patches has changed '
1791 'from %d to %d\n') %
1792 (len(old_guarded), len(guarded)))
1793 elif opts['series']:
1794 guards = {}
1795 noguards = 0
1796 for gs in q.series_guards:
1797 if not gs:
1798 noguards += 1
1799 for g in gs:
1800 guards.setdefault(g, 0)
1801 guards[g] += 1
1802 if ui.verbose:
1803 guards['NONE'] = noguards
1804 guards = guards.items()
1805 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1806 if guards:
1807 ui.note(_('guards in series file:\n'))
1808 for guard, count in guards:
1809 ui.note('%2d ' % count)
1810 ui.write(guard, '\n')
1811 else:
1812 ui.note(_('no guards in series file\n'))
1813 else:
1814 if guards:
1815 ui.note(_('active guards:\n'))
1816 for g in guards:
1817 ui.write(g, '\n')
1818 else:
1819 ui.write(_('no active guards\n'))
1820 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1821 popped = False
1822 if opts['pop'] or opts['reapply']:
1823 for i in xrange(len(q.applied)):
1824 pushable, reason = q.pushable(i)
1825 if not pushable:
1826 ui.status(_('popping guarded patches\n'))
1827 popped = True
1828 if i == 0:
1829 q.pop(repo, all=True)
1830 else:
1831 q.pop(repo, i-1)
1832 break
1833 if popped:
1834 try:
1835 if reapply:
1836 ui.status(_('reapplying unguarded patches\n'))
1837 q.push(repo, reapply)
1838 finally:
1839 q.save_dirty()
1272 1840
1273 1841 def reposetup(ui, repo):
1274 repomap[repo] = queue(ui, repo.join(""))
1842 class mqrepo(repo.__class__):
1843 def abort_if_wdir_patched(self, errmsg, force=False):
1844 if self.mq.applied and not force:
1845 parent = revlog.hex(self.dirstate.parents()[0])
1846 if parent in [s.rev for s in self.mq.applied]:
1847 raise util.Abort(errmsg)
1848
1849 def commit(self, *args, **opts):
1850 if len(args) >= 6:
1851 force = args[5]
1852 else:
1853 force = opts.get('force')
1854 self.abort_if_wdir_patched(
1855 _('cannot commit over an applied mq patch'),
1856 force)
1857
1858 return super(mqrepo, self).commit(*args, **opts)
1859
1860 def push(self, remote, force=False, revs=None):
1861 if self.mq.applied and not force:
1862 raise util.Abort(_('source has mq patches applied'))
1863 return super(mqrepo, self).push(remote, force, revs)
1864
1865 def tags(self):
1866 if self.tagscache:
1867 return self.tagscache
1868
1869 tagscache = super(mqrepo, self).tags()
1870
1871 q = self.mq
1872 if not q.applied:
1873 return tagscache
1874
1875 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1876 mqtags.append((mqtags[-1][0], 'qtip'))
1877 mqtags.append((mqtags[0][0], 'qbase'))
1878 for patch in mqtags:
1879 if patch[1] in tagscache:
1880 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1881 else:
1882 tagscache[patch[1]] = revlog.bin(patch[0])
1883
1884 return tagscache
1885
1886 if repo.local():
1887 repo.__class__ = mqrepo
1888 repo.mq = queue(ui, repo.join(""))
1275 1889
1276 1890 cmdtable = {
1277 1891 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1892 "qclone": (clone,
1893 [('', 'pull', None, _('use pull protocol to copy metadata')),
1894 ('U', 'noupdate', None, _('do not update the new working directories')),
1895 ('', 'uncompressed', None,
1896 _('use uncompressed transfer (fast over LAN)')),
1897 ('e', 'ssh', '', _('specify ssh command to use')),
1898 ('p', 'patches', '', _('location of source patch repo')),
1899 ('', 'remotecmd', '',
1900 _('specify hg command to run on the remote side'))],
1901 'hg qclone [OPTION]... SOURCE [DEST]'),
1278 1902 "qcommit|qci":
1279 1903 (commit,
1280 1904 commands.table["^commit|ci"][1],
1281 1905 'hg qcommit [OPTION]... [FILE]...'),
1282 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1283 "qdelete": (delete, [], 'hg qdelete PATCH'),
1906 "^qdiff": (diff,
1907 [('I', 'include', [], _('include names matching the given patterns')),
1908 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
1909 'hg qdiff [-I] [-X] [FILE]...'),
1910 "qdelete|qremove|qrm":
1911 (delete,
1912 [('k', 'keep', None, _('keep patch file'))],
1913 'hg qdelete [-k] PATCH'),
1914 'qfold':
1915 (fold,
1916 [('e', 'edit', None, _('edit patch header')),
1917 ('k', 'keep', None, _('keep folded patch files')),
1918 ('m', 'message', '', _('set patch header to <text>')),
1919 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1920 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1921 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
1922 ('n', 'none', None, _('drop all guards'))],
1923 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
1924 'qheader': (header, [],
1925 _('hg qheader [PATCH]')),
1284 1926 "^qimport":
1285 1927 (qimport,
1286 1928 [('e', 'existing', None, 'import file in patch dir'),
1287 1929 ('n', 'name', '', 'patch file name'),
1288 1930 ('f', 'force', None, 'overwrite existing files')],
1289 1931 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1290 1932 "^qinit":
1291 1933 (init,
1292 1934 [('c', 'create-repo', None, 'create queue repository')],
1293 1935 'hg qinit [-c]'),
1294 1936 "qnew":
1295 1937 (new,
1296 [('m', 'message', '', 'commit message'),
1297 ('f', 'force', None, 'force')],
1298 'hg qnew [-m TEXT] [-f] PATCH'),
1938 [('e', 'edit', None, _('edit commit message')),
1939 ('m', 'message', '', _('use <text> as commit message')),
1940 ('l', 'logfile', '', _('read the commit message from <file>')),
1941 ('f', 'force', None, _('import uncommitted changes into patch'))],
1942 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
1299 1943 "qnext": (next, [], 'hg qnext'),
1300 1944 "qprev": (prev, [], 'hg qprev'),
1301 1945 "^qpop":
1302 1946 (pop,
1303 1947 [('a', 'all', None, 'pop all patches'),
1304 1948 ('n', 'name', '', 'queue name to pop'),
1305 1949 ('f', 'force', None, 'forget any local changes')],
1306 1950 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1307 1951 "^qpush":
1308 1952 (push,
1309 1953 [('f', 'force', None, 'apply if the patch has rejects'),
1310 1954 ('l', 'list', None, 'list patch name in commit text'),
1311 1955 ('a', 'all', None, 'apply all patches'),
1312 1956 ('m', 'merge', None, 'merge from another queue'),
1313 1957 ('n', 'name', '', 'merge queue name')],
1314 1958 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1315 1959 "^qrefresh":
1316 1960 (refresh,
1317 [('s', 'short', None, 'short refresh')],
1318 'hg qrefresh [-s]'),
1961 [('e', 'edit', None, _('edit commit message')),
1962 ('m', 'message', '', _('change commit message with <text>')),
1963 ('l', 'logfile', '', _('change commit message with <file> content')),
1964 ('s', 'short', None, 'short refresh'),
1965 ('I', 'include', [], _('include names matching the given patterns')),
1966 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
1967 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] FILES...'),
1968 'qrename|qmv':
1969 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1319 1970 "qrestore":
1320 1971 (restore,
1321 1972 [('d', 'delete', None, 'delete save entry'),
1322 1973 ('u', 'update', None, 'update queue working dir')],
1323 1974 'hg qrestore [-d] [-u] REV'),
1324 1975 "qsave":
1325 1976 (save,
1326 [('m', 'message', '', 'commit message'),
1977 [('m', 'message', '', _('use <text> as commit message')),
1978 ('l', 'logfile', '', _('read the commit message from <file>')),
1327 1979 ('c', 'copy', None, 'copy patch directory'),
1328 1980 ('n', 'name', '', 'copy directory name'),
1329 1981 ('e', 'empty', None, 'clear queue status file'),
1330 1982 ('f', 'force', None, 'force copy')],
1331 'hg qsave [-m TEXT] [-c] [-n NAME] [-e] [-f]'),
1983 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1984 "qselect": (select,
1985 [('n', 'none', None, _('disable all guards')),
1986 ('s', 'series', None, _('list all guards in series file')),
1987 ('', 'pop', None,
1988 _('pop to before first guarded applied patch')),
1989 ('', 'reapply', None, _('pop, then reapply patches'))],
1990 'hg qselect [OPTION...] [GUARD...]'),
1332 1991 "qseries":
1333 1992 (series,
1334 [('m', 'missing', None, 'print patches not in series')],
1993 [('m', 'missing', None, 'print patches not in series'),
1994 ('s', 'summary', None, _('print first line of patch header'))],
1335 1995 'hg qseries [-m]'),
1336 1996 "^strip":
1337 1997 (strip,
1338 1998 [('f', 'force', None, 'force multi-head removal'),
1339 1999 ('b', 'backup', None, 'bundle unrelated changesets'),
1340 2000 ('n', 'nobackup', None, 'no backups')],
1341 2001 'hg strip [-f] [-b] [-n] REV'),
1342 2002 "qtop": (top, [], 'hg qtop'),
1343 2003 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1344 "qversion": (version, [], 'hg qversion')
1345 2004 }
1346
@@ -1,276 +1,276
1 1 # notify.py - email notifications 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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7 #
8 8 # hook extension to email notifications to people when changesets are
9 9 # committed to a repo they subscribe to.
10 10 #
11 11 # default mode is to print messages to stdout, for testing and
12 12 # configuring.
13 13 #
14 14 # to use, configure notify extension and enable in hgrc like this:
15 15 #
16 16 # [extensions]
17 17 # hgext.notify =
18 18 #
19 19 # [hooks]
20 20 # # one email for each incoming changeset
21 21 # incoming.notify = python:hgext.notify.hook
22 22 # # batch emails when many changesets incoming at one time
23 23 # changegroup.notify = python:hgext.notify.hook
24 24 #
25 25 # [notify]
26 26 # # config items go in here
27 27 #
28 28 # config items:
29 29 #
30 30 # REQUIRED:
31 31 # config = /path/to/file # file containing subscriptions
32 32 #
33 33 # OPTIONAL:
34 34 # test = True # print messages to stdout for testing
35 35 # strip = 3 # number of slashes to strip for url paths
36 36 # domain = example.com # domain to use if committer missing domain
37 37 # style = ... # style file to use when formatting email
38 38 # template = ... # template to use when formatting email
39 39 # incoming = ... # template to use when run as incoming hook
40 40 # changegroup = ... # template when run as changegroup hook
41 41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
42 42 # maxsubject = 67 # truncate subject line longer than this
43 43 # sources = serve # notify if source of incoming changes in this list
44 44 # # (serve == ssh or http, push, pull, bundle)
45 45 # [email]
46 46 # from = user@host.com # email address to send as if none given
47 47 # [web]
48 48 # baseurl = http://hgserver/... # root of hg web site for browsing commits
49 49 #
50 50 # notify config file has same format as regular hgrc. it has two
51 51 # sections so you can express subscriptions in whatever way is handier
52 52 # for you.
53 53 #
54 54 # [usersubs]
55 55 # # key is subscriber email, value is ","-separated list of glob patterns
56 56 # user@host = pattern
57 57 #
58 58 # [reposubs]
59 59 # # key is glob pattern, value is ","-separated list of subscriber emails
60 60 # pattern = user@host
61 61 #
62 62 # glob patterns are matched against path to repo root.
63 63 #
64 64 # if you like, you can put notify config file in repo that users can
65 65 # push changes to, they can manage their own subscriptions.
66 66
67 67 from mercurial.demandload import *
68 68 from mercurial.i18n import gettext as _
69 69 from mercurial.node import *
70 demandload(globals(), 'email.Parser mercurial:commands,templater,util')
71 demandload(globals(), 'fnmatch socket time')
70 demandload(globals(), 'mercurial:commands,patch,templater,util,mail')
71 demandload(globals(), 'email.Parser fnmatch socket time')
72 72
73 73 # template for single changeset can include email headers.
74 74 single_template = '''
75 75 Subject: changeset in {webroot}: {desc|firstline|strip}
76 76 From: {author}
77 77
78 78 changeset {node|short} in {root}
79 79 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
80 80 description:
81 81 \t{desc|tabindent|strip}
82 82 '''.lstrip()
83 83
84 84 # template for multiple changesets should not contain email headers,
85 85 # because only first set of headers will be used and result will look
86 86 # strange.
87 87 multiple_template = '''
88 88 changeset {node|short} in {root}
89 89 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
90 90 summary: {desc|firstline}
91 91 '''
92 92
93 93 deftemplates = {
94 94 'changegroup': multiple_template,
95 95 }
96 96
97 97 class notifier(object):
98 98 '''email notification class.'''
99 99
100 100 def __init__(self, ui, repo, hooktype):
101 101 self.ui = ui
102 102 cfg = self.ui.config('notify', 'config')
103 103 if cfg:
104 104 self.ui.readconfig(cfg)
105 105 self.repo = repo
106 106 self.stripcount = int(self.ui.config('notify', 'strip', 0))
107 107 self.root = self.strip(self.repo.root)
108 108 self.domain = self.ui.config('notify', 'domain')
109 109 self.sio = templater.stringio()
110 110 self.subs = self.subscribers()
111 111
112 112 mapfile = self.ui.config('notify', 'style')
113 113 template = (self.ui.config('notify', hooktype) or
114 114 self.ui.config('notify', 'template'))
115 115 self.t = templater.changeset_templater(self.ui, self.repo, mapfile,
116 116 self.sio)
117 117 if not mapfile and not template:
118 118 template = deftemplates.get(hooktype) or single_template
119 119 if template:
120 120 template = templater.parsestring(template, quoted=False)
121 121 self.t.use_template(template)
122 122
123 123 def strip(self, path):
124 124 '''strip leading slashes from local path, turn into web-safe path.'''
125 125
126 126 path = util.pconvert(path)
127 127 count = self.stripcount
128 128 while count > 0:
129 129 c = path.find('/')
130 130 if c == -1:
131 131 break
132 132 path = path[c+1:]
133 133 count -= 1
134 134 return path
135 135
136 136 def fixmail(self, addr):
137 137 '''try to clean up email addresses.'''
138 138
139 139 addr = templater.email(addr.strip())
140 140 a = addr.find('@localhost')
141 141 if a != -1:
142 142 addr = addr[:a]
143 143 if '@' not in addr:
144 144 return addr + '@' + self.domain
145 145 return addr
146 146
147 147 def subscribers(self):
148 148 '''return list of email addresses of subscribers to this repo.'''
149 149
150 150 subs = {}
151 151 for user, pats in self.ui.configitems('usersubs'):
152 152 for pat in pats.split(','):
153 153 if fnmatch.fnmatch(self.repo.root, pat.strip()):
154 154 subs[self.fixmail(user)] = 1
155 155 for pat, users in self.ui.configitems('reposubs'):
156 156 if fnmatch.fnmatch(self.repo.root, pat):
157 157 for user in users.split(','):
158 158 subs[self.fixmail(user)] = 1
159 159 subs = subs.keys()
160 160 subs.sort()
161 161 return subs
162 162
163 163 def url(self, path=None):
164 164 return self.ui.config('web', 'baseurl') + (path or self.root)
165 165
166 166 def node(self, node):
167 167 '''format one changeset.'''
168 168
169 169 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
170 170 baseurl=self.ui.config('web', 'baseurl'),
171 171 root=self.repo.root,
172 172 webroot=self.root)
173 173
174 174 def skipsource(self, source):
175 175 '''true if incoming changes from this source should be skipped.'''
176 176 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
177 177 return source not in ok_sources
178 178
179 179 def send(self, node, count):
180 180 '''send message.'''
181 181
182 182 p = email.Parser.Parser()
183 183 self.sio.seek(0)
184 184 msg = p.parse(self.sio)
185 185
186 186 def fix_subject():
187 187 '''try to make subject line exist and be useful.'''
188 188
189 189 subject = msg['Subject']
190 190 if not subject:
191 191 if count > 1:
192 192 subject = _('%s: %d new changesets') % (self.root, count)
193 193 else:
194 194 changes = self.repo.changelog.read(node)
195 195 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
196 196 subject = '%s: %s' % (self.root, s)
197 197 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
198 198 if maxsubject and len(subject) > maxsubject:
199 199 subject = subject[:maxsubject-3] + '...'
200 200 del msg['Subject']
201 201 msg['Subject'] = subject
202 202
203 203 def fix_sender():
204 204 '''try to make message have proper sender.'''
205 205
206 206 sender = msg['From']
207 207 if not sender:
208 208 sender = self.ui.config('email', 'from') or self.ui.username()
209 209 if '@' not in sender or '@localhost' in sender:
210 210 sender = self.fixmail(sender)
211 211 del msg['From']
212 212 msg['From'] = sender
213 213
214 214 fix_subject()
215 215 fix_sender()
216 216
217 217 msg['X-Hg-Notification'] = 'changeset ' + short(node)
218 218 if not msg['Message-Id']:
219 219 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
220 220 (short(node), int(time.time()),
221 221 hash(self.repo.root), socket.getfqdn()))
222 222 msg['To'] = ', '.join(self.subs)
223 223
224 224 msgtext = msg.as_string(0)
225 225 if self.ui.configbool('notify', 'test', True):
226 226 self.ui.write(msgtext)
227 227 if not msgtext.endswith('\n'):
228 228 self.ui.write('\n')
229 229 else:
230 230 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
231 231 (len(self.subs), count))
232 mail = self.ui.sendmail()
233 mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
232 mail.sendmail(self.ui, templater.email(msg['From']),
233 self.subs, msgtext)
234 234
235 235 def diff(self, node, ref):
236 236 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
237 237 if maxdiff == 0:
238 238 return
239 239 fp = templater.stringio()
240 240 prev = self.repo.changelog.parents(node)[0]
241 commands.dodiff(fp, self.ui, self.repo, prev, ref)
241 patch.diff(self.repo, fp, prev, ref)
242 242 difflines = fp.getvalue().splitlines(1)
243 243 if maxdiff > 0 and len(difflines) > maxdiff:
244 244 self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
245 245 (len(difflines), maxdiff))
246 246 difflines = difflines[:maxdiff]
247 247 elif difflines:
248 248 self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
249 249 self.sio.write(*difflines)
250 250
251 251 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
252 252 '''send email notifications to interested subscribers.
253 253
254 254 if used as changegroup hook, send one email for all changesets in
255 255 changegroup. else send one email per changeset.'''
256 256 n = notifier(ui, repo, hooktype)
257 257 if not n.subs:
258 ui.debug(_('notify: no subscribers to this repo\n'))
258 ui.debug(_('notify: no subscribers to repo %s\n' % n.root))
259 259 return
260 260 if n.skipsource(source):
261 261 ui.debug(_('notify: changes have source "%s" - skipping\n') %
262 262 source)
263 263 return
264 264 node = bin(node)
265 265 if hooktype == 'changegroup':
266 266 start = repo.changelog.rev(node)
267 267 end = repo.changelog.count()
268 268 count = end - start
269 269 for rev in xrange(start, end):
270 270 n.node(repo.changelog.node(rev))
271 271 n.diff(node, repo.changelog.tip())
272 272 else:
273 273 count = 1
274 274 n.node(node)
275 275 n.diff(node, node)
276 276 n.send(node, count)
@@ -1,278 +1,332
1 1 # Command for sending a collection of Mercurial changesets as a series
2 2 # of patch emails.
3 3 #
4 4 # The series is started off with a "[PATCH 0 of N]" introduction,
5 5 # which describes the series as a whole.
6 6 #
7 7 # Each patch email has a Subject line of "[PATCH M of N] ...", using
8 8 # the first line of the changeset description as the subject text.
9 9 # The message contains two or three body parts:
10 10 #
11 11 # The remainder of the changeset description.
12 12 #
13 13 # [Optional] If the diffstat program is installed, the result of
14 14 # running diffstat on the patch.
15 15 #
16 16 # The patch itself, as generated by "hg export".
17 17 #
18 18 # Each message refers to all of its predecessors using the In-Reply-To
19 19 # and References headers, so they will show up as a sequence in
20 20 # threaded mail and news readers, and in mail archives.
21 21 #
22 22 # For each changeset, you will be prompted with a diffstat summary and
23 23 # the changeset summary, so you can be sure you are sending the right
24 24 # changes.
25 25 #
26 # It is best to run this script with the "-n" (test only) flag before
27 # firing it up "for real", in which case it will use your pager to
28 # display each of the messages that it would send.
26 # To enable this extension:
29 27 #
30 # The "-m" (mbox) option will create an mbox file instead of sending
31 # the messages directly. This can be reviewed e.g. with "mutt -R -f mbox",
32 # and finally sent with "formail -s sendmail -bm -t < mbox".
28 # [extensions]
29 # hgext.patchbomb =
33 30 #
34 31 # To configure other defaults, add a section like this to your hgrc
35 32 # file:
36 33 #
37 # [email]
38 # from = My Name <my@email>
39 # to = recipient1, recipient2, ...
40 # cc = cc1, cc2, ...
34 # [email]
35 # from = My Name <my@email>
36 # to = recipient1, recipient2, ...
37 # cc = cc1, cc2, ...
38 # bcc = bcc1, bcc2, ...
39 #
40 # Then you can use the "hg email" command to mail a series of changesets
41 # as a patchbomb.
42 #
43 # To avoid sending patches prematurely, it is a good idea to first run
44 # the "email" command with the "-n" option (test only). You will be
45 # prompted for an email recipient address, a subject an an introductory
46 # message describing the patches of your patchbomb. Then when all is
47 # done, your pager will be fired up once for each patchbomb message, so
48 # you can verify everything is alright.
49 #
50 # The "-m" (mbox) option is also very useful. Instead of previewing
51 # each patchbomb message in a pager or sending the messages directly,
52 # it will create a UNIX mailbox file with the patch emails. This
53 # mailbox file can be previewed with any mail user agent which supports
54 # UNIX mbox files, i.e. with mutt:
55 #
56 # % mutt -R -f mbox
57 #
58 # When you are previewing the patchbomb messages, you can use `formail'
59 # (a utility that is commonly installed as part of the procmail package),
60 # to send each message out:
61 #
62 # % formail -s sendmail -bm -t < mbox
63 #
64 # That should be all. Now your patchbomb is on its way out.
41 65
42 66 from mercurial.demandload import *
43 67 demandload(globals(), '''email.MIMEMultipart email.MIMEText email.Utils
44 mercurial:commands,hg,ui
68 mercurial:commands,hg,mail,ui
45 69 os errno popen2 socket sys tempfile time''')
46 70 from mercurial.i18n import gettext as _
71 from mercurial.node import *
47 72
48 73 try:
49 74 # readline gives raw_input editing capabilities, but is not
50 75 # present on windows
51 76 import readline
52 77 except ImportError: pass
53 78
54 79 def diffstat(patch):
55 80 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
56 81 try:
57 82 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
58 83 try:
59 84 for line in patch: print >> p.tochild, line
60 85 p.tochild.close()
61 86 if p.wait(): return
62 87 fp = os.fdopen(fd, 'r')
63 88 stat = []
64 89 for line in fp: stat.append(line.lstrip())
65 90 last = stat.pop()
66 91 stat.insert(0, last)
67 92 stat = ''.join(stat)
68 93 if stat.startswith('0 files'): raise ValueError
69 94 return stat
70 95 except: raise
71 96 finally:
72 97 try: os.unlink(name)
73 98 except: pass
74 99
75 100 def patchbomb(ui, repo, *revs, **opts):
76 101 '''send changesets as a series of patch emails
77 102
78 103 The series starts with a "[PATCH 0 of N]" introduction, which
79 104 describes the series as a whole.
80 105
81 106 Each patch email has a Subject line of "[PATCH M of N] ...", using
82 107 the first line of the changeset description as the subject text.
83 108 The message contains two or three body parts. First, the rest of
84 109 the changeset description. Next, (optionally) if the diffstat
85 110 program is installed, the result of running diffstat on the patch.
86 111 Finally, the patch itself, as generated by "hg export".'''
87 112 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
88 113 if default: prompt += ' [%s]' % default
89 114 prompt += rest
90 115 while True:
91 116 r = raw_input(prompt)
92 117 if r: return r
93 118 if default is not None: return default
94 119 if empty_ok: return r
95 120 ui.warn(_('Please enter a valid value.\n'))
96 121
97 122 def confirm(s):
98 123 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
99 124 raise ValueError
100 125
101 126 def cdiffstat(summary, patch):
102 127 s = diffstat(patch)
103 128 if s:
104 129 if summary:
105 130 ui.write(summary, '\n')
106 131 ui.write(s, '\n')
107 132 confirm(_('Does the diffstat above look okay'))
108 133 return s
109 134
110 135 def makepatch(patch, idx, total):
111 136 desc = []
112 137 node = None
113 138 body = ''
114 139 for line in patch:
115 140 if line.startswith('#'):
116 141 if line.startswith('# Node ID'): node = line.split()[-1]
117 142 continue
118 143 if line.startswith('diff -r'): break
119 144 desc.append(line)
120 145 if not node: raise ValueError
121 146
122 147 #body = ('\n'.join(desc[1:]).strip() or
123 148 # 'Patch subject is complete summary.')
124 149 #body += '\n\n\n'
125 150
126 151 if opts['plain']:
127 152 while patch and patch[0].startswith('# '): patch.pop(0)
128 153 if patch: patch.pop(0)
129 154 while patch and not patch[0].strip(): patch.pop(0)
130 155 if opts['diffstat']:
131 156 body += cdiffstat('\n'.join(desc), patch) + '\n\n'
132 body += '\n'.join(patch)
133 msg = email.MIMEText.MIMEText(body)
157 if opts['attach']:
158 msg = email.MIMEMultipart.MIMEMultipart()
159 if body: msg.attach(email.MIMEText.MIMEText(body, 'plain'))
160 p = email.MIMEText.MIMEText('\n'.join(patch), 'x-patch')
161 binnode = bin(node)
162 # if node is mq patch, it will have patch file name as tag
163 patchname = [t for t in repo.nodetags(binnode)
164 if t.endswith('.patch') or t.endswith('.diff')]
165 if patchname:
166 patchname = patchname[0]
167 elif total > 1:
168 patchname = commands.make_filename(repo, '%b-%n.patch',
169 binnode, idx, total)
170 else:
171 patchname = commands.make_filename(repo, '%b.patch', binnode)
172 p['Content-Disposition'] = 'inline; filename=' + patchname
173 msg.attach(p)
174 else:
175 body += '\n'.join(patch)
176 msg = email.MIMEText.MIMEText(body)
134 177 if total == 1:
135 178 subj = '[PATCH] ' + desc[0].strip()
136 179 else:
137 180 subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip())
138 181 if subj.endswith('.'): subj = subj[:-1]
139 182 msg['Subject'] = subj
140 183 msg['X-Mercurial-Node'] = node
141 184 return msg
142 185
143 186 start_time = int(time.time())
144 187
145 188 def genmsgid(id):
146 189 return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn())
147 190
148 191 patches = []
149 192
150 193 class exportee:
151 194 def __init__(self, container):
152 195 self.lines = []
153 196 self.container = container
154 197 self.name = 'email'
155 198
156 199 def write(self, data):
157 200 self.lines.append(data)
158 201
159 202 def close(self):
160 203 self.container.append(''.join(self.lines).split('\n'))
161 204 self.lines = []
162 205
163 206 commands.export(ui, repo, *revs, **{'output': exportee(patches),
164 207 'switch_parent': False,
165 208 'text': None})
166 209
167 210 jumbo = []
168 211 msgs = []
169 212
170 213 ui.write(_('This patch series consists of %d patches.\n\n') % len(patches))
171 214
172 215 for p, i in zip(patches, range(len(patches))):
173 216 jumbo.extend(p)
174 217 msgs.append(makepatch(p, i + 1, len(patches)))
175 218
176 219 sender = (opts['from'] or ui.config('email', 'from') or
177 220 ui.config('patchbomb', 'from') or
178 221 prompt('From', ui.username()))
179 222
180 223 def getaddrs(opt, prpt, default = None):
181 224 addrs = opts[opt] or (ui.config('email', opt) or
182 225 ui.config('patchbomb', opt) or
183 226 prompt(prpt, default = default)).split(',')
184 227 return [a.strip() for a in addrs if a.strip()]
185 228 to = getaddrs('to', 'To')
186 229 cc = getaddrs('cc', 'Cc', '')
187 230
231 bcc = opts['bcc'] or (ui.config('email', 'bcc') or
232 ui.config('patchbomb', 'bcc') or '').split(',')
233 bcc = [a.strip() for a in bcc if a.strip()]
234
188 235 if len(patches) > 1:
189 236 ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
190 237
191 msg = email.MIMEMultipart.MIMEMultipart()
192 msg['Subject'] = '[PATCH 0 of %d] %s' % (
238 subj = '[PATCH 0 of %d] %s' % (
193 239 len(patches),
194 240 opts['subject'] or
195 241 prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches)))
196 242
197 243 ui.write(_('Finish with ^D or a dot on a line by itself.\n\n'))
198 244
199 245 body = []
200 246
201 247 while True:
202 248 try: l = raw_input()
203 249 except EOFError: break
204 250 if l == '.': break
205 251 body.append(l)
206 252
207 msg.attach(email.MIMEText.MIMEText('\n'.join(body) + '\n'))
208
209 253 if opts['diffstat']:
210 254 d = cdiffstat(_('Final summary:\n'), jumbo)
211 if d: msg.attach(email.MIMEText.MIMEText(d))
255 if d: body.append('\n' + d)
256
257 body = '\n'.join(body) + '\n'
258
259 msg = email.MIMEText.MIMEText(body)
260 msg['Subject'] = subj
212 261
213 262 msgs.insert(0, msg)
214 263
215 264 ui.write('\n')
216 265
217 266 if not opts['test'] and not opts['mbox']:
218 mail = ui.sendmail()
267 mailer = mail.connect(ui)
219 268 parent = None
220 269
221 270 # Calculate UTC offset
222 271 if time.daylight: offset = time.altzone
223 272 else: offset = time.timezone
224 273 if offset <= 0: sign, offset = '+', -offset
225 274 else: sign = '-'
226 275 offset = '%s%02d%02d' % (sign, offset / 3600, (offset % 3600) / 60)
227 276
228 277 sender_addr = email.Utils.parseaddr(sender)[1]
229 278 for m in msgs:
230 279 try:
231 280 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
232 281 except TypeError:
233 282 m['Message-Id'] = genmsgid('patchbomb')
234 283 if parent:
235 284 m['In-Reply-To'] = parent
236 285 else:
237 286 parent = m['Message-Id']
238 287 m['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime(start_time)) + ' ' + offset
239 288
240 289 start_time += 1
241 290 m['From'] = sender
242 291 m['To'] = ', '.join(to)
243 if cc: m['Cc'] = ', '.join(cc)
292 if cc: m['Cc'] = ', '.join(cc)
293 if bcc: m['Bcc'] = ', '.join(bcc)
244 294 if opts['test']:
245 295 ui.status('Displaying ', m['Subject'], ' ...\n')
246 296 fp = os.popen(os.getenv('PAGER', 'more'), 'w')
247 297 try:
248 298 fp.write(m.as_string(0))
249 299 fp.write('\n')
250 300 except IOError, inst:
251 301 if inst.errno != errno.EPIPE:
252 302 raise
253 303 fp.close()
254 304 elif opts['mbox']:
255 305 ui.status('Writing ', m['Subject'], ' ...\n')
256 306 fp = open(opts['mbox'], m.has_key('In-Reply-To') and 'ab+' or 'wb+')
257 307 date = time.asctime(time.localtime(start_time))
258 308 fp.write('From %s %s\n' % (sender_addr, date))
259 309 fp.write(m.as_string(0))
260 310 fp.write('\n\n')
261 311 fp.close()
262 312 else:
263 313 ui.status('Sending ', m['Subject'], ' ...\n')
264 mail.sendmail(sender, to + cc, m.as_string(0))
314 # Exim does not remove the Bcc field
315 del m['Bcc']
316 mailer.sendmail(sender, to + bcc + cc, m.as_string(0))
265 317
266 318 cmdtable = {
267 319 'email':
268 320 (patchbomb,
269 [('c', 'cc', [], 'email addresses of copy recipients'),
321 [('a', 'attach', None, 'send patches as inline attachments'),
322 ('', 'bcc', [], 'email addresses of blind copy recipients'),
323 ('c', 'cc', [], 'email addresses of copy recipients'),
270 324 ('d', 'diffstat', None, 'add diffstat output to messages'),
271 325 ('f', 'from', '', 'email address of sender'),
272 326 ('', 'plain', None, 'omit hg patch header'),
273 327 ('n', 'test', None, 'print messages that would be sent'),
274 328 ('m', 'mbox', '', 'write messages to mbox file instead of sending them'),
275 329 ('s', 'subject', '', 'subject of introductory message'),
276 330 ('t', 'to', [], 'email addresses of recipients')],
277 331 "hg email [OPTION]... [REV]...")
278 332 }
@@ -1,174 +1,174
1 1 # archival.py - revision archival 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
6 6 # the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import *
9 9 from i18n import gettext as _
10 10 from node import *
11 11 demandload(globals(), 'cStringIO os stat tarfile time util zipfile')
12 12
13 13 def tidyprefix(dest, prefix, suffixes):
14 14 '''choose prefix to use for names in archive. make sure prefix is
15 15 safe for consumers.'''
16 16
17 17 if prefix:
18 18 prefix = prefix.replace('\\', '/')
19 19 else:
20 20 if not isinstance(dest, str):
21 21 raise ValueError('dest must be string if no prefix')
22 22 prefix = os.path.basename(dest)
23 23 lower = prefix.lower()
24 24 for sfx in suffixes:
25 25 if lower.endswith(sfx):
26 26 prefix = prefix[:-len(sfx)]
27 27 break
28 28 lpfx = os.path.normpath(util.localpath(prefix))
29 29 prefix = util.pconvert(lpfx)
30 30 if not prefix.endswith('/'):
31 31 prefix += '/'
32 32 if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix:
33 33 raise util.Abort(_('archive prefix contains illegal components'))
34 34 return prefix
35 35
36 36 class tarit:
37 37 '''write archive to tar file or stream. can write uncompressed,
38 38 or compress with gzip or bzip2.'''
39 39
40 40 def __init__(self, dest, prefix, mtime, kind=''):
41 41 self.prefix = tidyprefix(dest, prefix, ['.tar', '.tar.bz2', '.tar.gz',
42 42 '.tgz', 'tbz2'])
43 43 self.mtime = mtime
44 44 if isinstance(dest, str):
45 45 self.z = tarfile.open(dest, mode='w:'+kind)
46 46 else:
47 47 self.z = tarfile.open(mode='w|'+kind, fileobj=dest)
48 48
49 49 def addfile(self, name, mode, data):
50 50 i = tarfile.TarInfo(self.prefix + name)
51 51 i.mtime = self.mtime
52 52 i.size = len(data)
53 53 i.mode = mode
54 54 self.z.addfile(i, cStringIO.StringIO(data))
55 55
56 56 def done(self):
57 57 self.z.close()
58 58
59 59 class tellable:
60 60 '''provide tell method for zipfile.ZipFile when writing to http
61 61 response file object.'''
62 62
63 63 def __init__(self, fp):
64 64 self.fp = fp
65 65 self.offset = 0
66 66
67 67 def __getattr__(self, key):
68 68 return getattr(self.fp, key)
69 69
70 70 def write(self, s):
71 71 self.fp.write(s)
72 72 self.offset += len(s)
73 73
74 74 def tell(self):
75 75 return self.offset
76 76
77 77 class zipit:
78 78 '''write archive to zip file or stream. can write uncompressed,
79 79 or compressed with deflate.'''
80 80
81 81 def __init__(self, dest, prefix, mtime, compress=True):
82 82 self.prefix = tidyprefix(dest, prefix, ('.zip',))
83 83 if not isinstance(dest, str):
84 84 try:
85 85 dest.tell()
86 86 except (AttributeError, IOError):
87 87 dest = tellable(dest)
88 88 self.z = zipfile.ZipFile(dest, 'w',
89 89 compress and zipfile.ZIP_DEFLATED or
90 90 zipfile.ZIP_STORED)
91 91 self.date_time = time.gmtime(mtime)[:6]
92 92
93 93 def addfile(self, name, mode, data):
94 94 i = zipfile.ZipInfo(self.prefix + name, self.date_time)
95 95 i.compress_type = self.z.compression
96 96 i.flag_bits = 0x08
97 97 # unzip will not honor unix file modes unless file creator is
98 98 # set to unix (id 3).
99 99 i.create_system = 3
100 100 i.external_attr = (mode | stat.S_IFREG) << 16L
101 101 self.z.writestr(i, data)
102 102
103 103 def done(self):
104 104 self.z.close()
105 105
106 106 class fileit:
107 107 '''write archive as files in directory.'''
108 108
109 109 def __init__(self, name, prefix, mtime):
110 110 if prefix:
111 111 raise util.Abort(_('cannot give prefix when archiving to files'))
112 112 self.basedir = name
113 113 self.dirs = {}
114 114 self.oflags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY |
115 115 getattr(os, 'O_BINARY', 0) |
116 116 getattr(os, 'O_NOFOLLOW', 0))
117 117
118 118 def addfile(self, name, mode, data):
119 119 destfile = os.path.join(self.basedir, name)
120 120 destdir = os.path.dirname(destfile)
121 121 if destdir not in self.dirs:
122 122 if not os.path.isdir(destdir):
123 123 os.makedirs(destdir)
124 124 self.dirs[destdir] = 1
125 125 os.fdopen(os.open(destfile, self.oflags, mode), 'wb').write(data)
126 126
127 127 def done(self):
128 128 pass
129 129
130 130 archivers = {
131 131 'files': fileit,
132 132 'tar': tarit,
133 133 'tbz2': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'bz2'),
134 134 'tgz': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'gz'),
135 135 'uzip': lambda name, prefix, mtime: zipit(name, prefix, mtime, False),
136 136 'zip': zipit,
137 137 }
138 138
139 139 def archive(repo, dest, node, kind, decode=True, matchfn=None,
140 140 prefix=None, mtime=None):
141 141 '''create archive of repo as it was at node.
142 142
143 143 dest can be name of directory, name of archive file, or file
144 144 object to write archive to.
145 145
146 146 kind is type of archive to create.
147 147
148 148 decode tells whether to put files through decode filters from
149 149 hgrc.
150 150
151 151 matchfn is function to filter names of files to write to archive.
152 152
153 153 prefix is name of path to put before every archive member.'''
154 154
155 155 def write(name, mode, data):
156 156 if matchfn and not matchfn(name): return
157 157 if decode:
158 158 fp = cStringIO.StringIO()
159 159 repo.wwrite(name, data, fp)
160 160 data = fp.getvalue()
161 161 archiver.addfile(name, mode, data)
162 162
163 163 change = repo.changelog.read(node)
164 164 mn = change[0]
165 165 archiver = archivers[kind](dest, prefix, mtime or change[2][0])
166 mf = repo.manifest.read(mn).items()
167 mff = repo.manifest.readflags(mn)
168 mf.sort()
166 m = repo.manifest.read(mn)
167 items = m.items()
168 items.sort()
169 169 write('.hg_archival.txt', 0644,
170 170 'repo: %s\nnode: %s\n' % (hex(repo.changelog.node(0)), hex(node)))
171 for filename, filenode in mf:
172 write(filename, mff[filename] and 0755 or 0644,
171 for filename, filenode in items:
172 write(filename, m.execf(filename) and 0755 or 0644,
173 173 repo.file(filename).read(filenode))
174 174 archiver.done()
@@ -1,371 +1,371
1 1 /*
2 2 bdiff.c - efficient binary diff extension for Mercurial
3 3
4 Copyright 2005 Matt Mackall <mpm@selenic.com>
4 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5
6 6 This software may be used and distributed according to the terms of
7 7 the GNU General Public License, incorporated herein by reference.
8 8
9 9 Based roughly on Python difflib
10 10 */
11 11
12 12 #include <Python.h>
13 13 #include <stdlib.h>
14 14 #include <string.h>
15 15
16 16 #ifdef __hpux
17 17 #define inline
18 18 #endif
19 19
20 20 #ifdef __SUNPRO_C
21 21 # define inline
22 22 #endif
23 23
24 24 #ifdef _WIN32
25 25 #ifdef _MSC_VER
26 26 #define inline __inline
27 27 typedef unsigned long uint32_t;
28 28 #else
29 29 #include <stdint.h>
30 30 #endif
31 31 static uint32_t htonl(uint32_t x)
32 32 {
33 33 return ((x & 0x000000ffUL) << 24) |
34 34 ((x & 0x0000ff00UL) << 8) |
35 35 ((x & 0x00ff0000UL) >> 8) |
36 36 ((x & 0xff000000UL) >> 24);
37 37 }
38 38 #else
39 39 #include <sys/types.h>
40 40 #include <arpa/inet.h>
41 41 #include <inttypes.h>
42 42 #endif
43 43
44 44 struct line {
45 45 int h, len, n, e;
46 46 const char *l;
47 47 };
48 48
49 49 struct pos {
50 50 int pos, len;
51 51 };
52 52
53 53 struct hunk {
54 54 int a1, a2, b1, b2;
55 55 };
56 56
57 57 struct hunklist {
58 58 struct hunk *base, *head;
59 59 };
60 60
61 61 static inline uint32_t rol32(uint32_t word, unsigned int shift)
62 62 {
63 63 return (word << shift) | (word >> (32 - shift));
64 64 }
65 65
66 66 int splitlines(const char *a, int len, struct line **lr)
67 67 {
68 68 int g, h, i;
69 69 const char *p, *b = a;
70 70 struct line *l;
71 71
72 72 /* count the lines */
73 73 i = 1; /* extra line for sentinel */
74 74 for (p = a; p < a + len; p++)
75 75 if (*p == '\n' || p == a + len - 1)
76 76 i++;
77 77
78 78 *lr = l = (struct line *)malloc(sizeof(struct line) * i);
79 79 if (!l)
80 80 return -1;
81 81
82 82 /* build the line array and calculate hashes */
83 83 h = 0;
84 84 for (p = a; p < a + len; p++) {
85 85 /*
86 86 * a simple hash from GNU diff, with better collision
87 87 * resistance from hashpjw. this slows down common
88 88 * case by 10%, but speeds up worst case by 100x.
89 89 */
90 90 h = *p + rol32(h, 7);
91 91 if ((g = h & 0xf0000000)) {
92 92 h ^= g >> 24;
93 93 h ^= g;
94 94 }
95 95 if (*p == '\n' || p == a + len - 1) {
96 96 l->len = p - b + 1;
97 97 l->h = h * l->len;
98 98 l->l = b;
99 99 l->n = -1;
100 100 l++;
101 101 b = p + 1;
102 102 h = 0;
103 103 }
104 104 }
105 105
106 106 /* set up a sentinel */
107 107 l->h = l->len = 0;
108 108 l->l = a + len;
109 109 return i - 1;
110 110 }
111 111
112 112 int inline cmp(struct line *a, struct line *b)
113 113 {
114 114 return a->h != b->h || a->len != b->len || memcmp(a->l, b->l, a->len);
115 115 }
116 116
117 117 static int equatelines(struct line *a, int an, struct line *b, int bn)
118 118 {
119 119 int i, j, buckets = 1, t;
120 120 struct pos *h;
121 121
122 122 /* build a hash table of the next highest power of 2 */
123 123 while (buckets < bn + 1)
124 124 buckets *= 2;
125 125
126 126 h = (struct pos *)malloc(buckets * sizeof(struct pos));
127 127 buckets = buckets - 1;
128 128 if (!h)
129 129 return 0;
130 130
131 131 /* clear the hash table */
132 132 for (i = 0; i <= buckets; i++) {
133 133 h[i].pos = -1;
134 134 h[i].len = 0;
135 135 }
136 136
137 137 /* add lines to the hash table chains */
138 138 for (i = bn - 1; i >= 0; i--) {
139 139 /* find the equivalence class */
140 140 for (j = b[i].h & buckets; h[j].pos != -1;
141 141 j = (j + 1) & buckets)
142 142 if (!cmp(b + i, b + h[j].pos))
143 143 break;
144 144
145 145 /* add to the head of the equivalence class */
146 146 b[i].n = h[j].pos;
147 147 b[i].e = j;
148 148 h[j].pos = i;
149 149 h[j].len++; /* keep track of popularity */
150 150 }
151 151
152 152 /* compute popularity threshold */
153 153 t = (bn >= 200) ? bn / 100 : bn + 1;
154 154
155 155 /* match items in a to their equivalence class in b */
156 156 for (i = 0; i < an; i++) {
157 157 /* find the equivalence class */
158 158 for (j = a[i].h & buckets; h[j].pos != -1;
159 159 j = (j + 1) & buckets)
160 160 if (!cmp(a + i, b + h[j].pos))
161 161 break;
162 162
163 163 a[i].e = j; /* use equivalence class for quick compare */
164 164 if (h[j].len <= t)
165 165 a[i].n = h[j].pos; /* point to head of match list */
166 166 else
167 167 a[i].n = -1; /* too popular */
168 168 }
169 169
170 170 /* discard hash tables */
171 171 free(h);
172 172 return 1;
173 173 }
174 174
175 175 static int longest_match(struct line *a, struct line *b, struct pos *pos,
176 176 int a1, int a2, int b1, int b2, int *omi, int *omj)
177 177 {
178 178 int mi = a1, mj = b1, mk = 0, mb = 0, i, j, k;
179 179
180 180 for (i = a1; i < a2; i++) {
181 181 /* skip things before the current block */
182 182 for (j = a[i].n; j != -1 && j < b1; j = b[j].n)
183 183 ;
184 184
185 185 /* loop through all lines match a[i] in b */
186 186 for (; j != -1 && j < b2; j = b[j].n) {
187 187 /* does this extend an earlier match? */
188 188 if (i > a1 && j > b1 && pos[j - 1].pos == i - 1)
189 189 k = pos[j - 1].len + 1;
190 190 else
191 191 k = 1;
192 192 pos[j].pos = i;
193 193 pos[j].len = k;
194 194
195 195 /* best match so far? */
196 196 if (k > mk) {
197 197 mi = i;
198 198 mj = j;
199 199 mk = k;
200 200 }
201 201 }
202 202 }
203 203
204 204 if (mk) {
205 205 mi = mi - mk + 1;
206 206 mj = mj - mk + 1;
207 207 }
208 208
209 209 /* expand match to include neighboring popular lines */
210 210 while (mi - mb > a1 && mj - mb > b1 &&
211 211 a[mi - mb - 1].e == b[mj - mb - 1].e)
212 212 mb++;
213 213 while (mi + mk < a2 && mj + mk < b2 &&
214 214 a[mi + mk].e == b[mj + mk].e)
215 215 mk++;
216 216
217 217 *omi = mi - mb;
218 218 *omj = mj - mb;
219 219 return mk + mb;
220 220 }
221 221
222 222 static void recurse(struct line *a, struct line *b, struct pos *pos,
223 223 int a1, int a2, int b1, int b2, struct hunklist *l)
224 224 {
225 225 int i, j, k;
226 226
227 227 /* find the longest match in this chunk */
228 228 k = longest_match(a, b, pos, a1, a2, b1, b2, &i, &j);
229 229 if (!k)
230 230 return;
231 231
232 232 /* and recurse on the remaining chunks on either side */
233 233 recurse(a, b, pos, a1, i, b1, j, l);
234 234 l->head->a1 = i;
235 235 l->head->a2 = i + k;
236 236 l->head->b1 = j;
237 237 l->head->b2 = j + k;
238 238 l->head++;
239 239 recurse(a, b, pos, i + k, a2, j + k, b2, l);
240 240 }
241 241
242 242 static struct hunklist diff(struct line *a, int an, struct line *b, int bn)
243 243 {
244 244 struct hunklist l;
245 245 struct pos *pos;
246 246 int t;
247 247
248 248 /* allocate and fill arrays */
249 249 t = equatelines(a, an, b, bn);
250 250 pos = (struct pos *)calloc(bn, sizeof(struct pos));
251 251 /* we can't have more matches than lines in the shorter file */
252 252 l.head = l.base = (struct hunk *)malloc(sizeof(struct hunk) *
253 253 ((an<bn ? an:bn) + 1));
254 254
255 255 if (pos && l.base && t) {
256 256 /* generate the matching block list */
257 257 recurse(a, b, pos, 0, an, 0, bn, &l);
258 258 l.head->a1 = an;
259 259 l.head->b1 = bn;
260 260 l.head++;
261 261 }
262 262
263 263 free(pos);
264 264 return l;
265 265 }
266 266
267 267 static PyObject *blocks(PyObject *self, PyObject *args)
268 268 {
269 269 PyObject *sa, *sb, *rl = NULL, *m;
270 270 struct line *a, *b;
271 271 struct hunklist l = {NULL, NULL};
272 272 struct hunk *h;
273 273 int an, bn, pos = 0;
274 274
275 275 if (!PyArg_ParseTuple(args, "SS:bdiff", &sa, &sb))
276 276 return NULL;
277 277
278 278 an = splitlines(PyString_AsString(sa), PyString_Size(sa), &a);
279 279 bn = splitlines(PyString_AsString(sb), PyString_Size(sb), &b);
280 280 if (!a || !b)
281 281 goto nomem;
282 282
283 283 l = diff(a, an, b, bn);
284 284 rl = PyList_New(l.head - l.base);
285 285 if (!l.head || !rl)
286 286 goto nomem;
287 287
288 288 for (h = l.base; h != l.head; h++) {
289 289 m = Py_BuildValue("iiii", h->a1, h->a2, h->b1, h->b2);
290 290 PyList_SetItem(rl, pos, m);
291 291 pos++;
292 292 }
293 293
294 294 nomem:
295 295 free(a);
296 296 free(b);
297 297 free(l.base);
298 298 return rl ? rl : PyErr_NoMemory();
299 299 }
300 300
301 301 static PyObject *bdiff(PyObject *self, PyObject *args)
302 302 {
303 303 PyObject *sa, *sb, *result = NULL;
304 304 struct line *al, *bl;
305 305 struct hunklist l = {NULL, NULL};
306 306 struct hunk *h;
307 307 char encode[12], *rb;
308 308 int an, bn, len = 0, la = 0, lb = 0;
309 309
310 310 if (!PyArg_ParseTuple(args, "SS:bdiff", &sa, &sb))
311 311 return NULL;
312 312
313 313 an = splitlines(PyString_AsString(sa), PyString_Size(sa), &al);
314 314 bn = splitlines(PyString_AsString(sb), PyString_Size(sb), &bl);
315 315 if (!al || !bl)
316 316 goto nomem;
317 317
318 318 l = diff(al, an, bl, bn);
319 319 if (!l.head)
320 320 goto nomem;
321 321
322 322 /* calculate length of output */
323 323 for (h = l.base; h != l.head; h++) {
324 324 if (h->a1 != la || h->b1 != lb)
325 325 len += 12 + bl[h->b1].l - bl[lb].l;
326 326 la = h->a2;
327 327 lb = h->b2;
328 328 }
329 329
330 330 result = PyString_FromStringAndSize(NULL, len);
331 331 if (!result)
332 332 goto nomem;
333 333
334 334 /* build binary patch */
335 335 rb = PyString_AsString(result);
336 336 la = lb = 0;
337 337
338 338 for (h = l.base; h != l.head; h++) {
339 339 if (h->a1 != la || h->b1 != lb) {
340 340 len = bl[h->b1].l - bl[lb].l;
341 341 *(uint32_t *)(encode) = htonl(al[la].l - al->l);
342 342 *(uint32_t *)(encode + 4) = htonl(al[h->a1].l - al->l);
343 343 *(uint32_t *)(encode + 8) = htonl(len);
344 344 memcpy(rb, encode, 12);
345 345 memcpy(rb + 12, bl[lb].l, len);
346 346 rb += 12 + len;
347 347 }
348 348 la = h->a2;
349 349 lb = h->b2;
350 350 }
351 351
352 352 nomem:
353 353 free(al);
354 354 free(bl);
355 355 free(l.base);
356 356 return result ? result : PyErr_NoMemory();
357 357 }
358 358
359 359 static char mdiff_doc[] = "Efficient binary diff.";
360 360
361 361 static PyMethodDef methods[] = {
362 362 {"bdiff", bdiff, METH_VARARGS, "calculate a binary diff\n"},
363 363 {"blocks", blocks, METH_VARARGS, "find a list of matching lines\n"},
364 364 {NULL, NULL}
365 365 };
366 366
367 367 PyMODINIT_FUNC initbdiff(void)
368 368 {
369 369 Py_InitModule3("bdiff", methods, mdiff_doc);
370 370 }
371 371
@@ -1,232 +1,254
1 1 """
2 2 bundlerepo.py - repository class for viewing uncompressed bundles
3 3
4 4 This provides a read-only repository interface to bundles as if
5 5 they were part of the actual repository.
6 6
7 7 Copyright 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
8 8
9 9 This software may be used and distributed according to the terms
10 10 of the GNU General Public License, incorporated herein by reference.
11 11 """
12 12
13 13 from node import *
14 14 from i18n import gettext as _
15 15 from demandload import demandload
16 16 demandload(globals(), "changegroup util os struct bz2 tempfile")
17 17
18 18 import localrepo, changelog, manifest, filelog, revlog
19 19
20 20 class bundlerevlog(revlog.revlog):
21 21 def __init__(self, opener, indexfile, datafile, bundlefile,
22 22 linkmapper=None):
23 23 # How it works:
24 24 # to retrieve a revision, we need to know the offset of
25 25 # the revision in the bundlefile (an opened file).
26 26 #
27 27 # We store this offset in the index (start), to differentiate a
28 28 # rev in the bundle and from a rev in the revlog, we check
29 29 # len(index[r]). If the tuple is bigger than 7, it is a bundle
30 30 # (it is bigger since we store the node to which the delta is)
31 31 #
32 32 revlog.revlog.__init__(self, opener, indexfile, datafile)
33 33 self.bundlefile = bundlefile
34 34 self.basemap = {}
35 35 def chunkpositer():
36 36 for chunk in changegroup.chunkiter(bundlefile):
37 37 pos = bundlefile.tell()
38 38 yield chunk, pos - len(chunk)
39 39 n = self.count()
40 40 prev = None
41 41 for chunk, start in chunkpositer():
42 42 size = len(chunk)
43 43 if size < 80:
44 44 raise util.Abort("invalid changegroup")
45 45 start += 80
46 46 size -= 80
47 47 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
48 48 if node in self.nodemap:
49 49 prev = node
50 50 continue
51 51 for p in (p1, p2):
52 52 if not p in self.nodemap:
53 53 raise revlog.RevlogError(_("unknown parent %s") % short(p1))
54 54 if linkmapper is None:
55 55 link = n
56 56 else:
57 57 link = linkmapper(cs)
58 58
59 59 if not prev:
60 60 prev = p1
61 61 # start, size, base is not used, link, p1, p2, delta ref
62 62 if self.version == 0:
63 63 e = (start, size, None, link, p1, p2, node)
64 64 else:
65 65 e = (self.offset_type(start, 0), size, -1, None, link,
66 66 self.rev(p1), self.rev(p2), node)
67 67 self.basemap[n] = prev
68 68 self.index.append(e)
69 69 self.nodemap[node] = n
70 70 prev = node
71 71 n += 1
72 72
73 73 def bundle(self, rev):
74 74 """is rev from the bundle"""
75 75 if rev < 0:
76 76 return False
77 77 return rev in self.basemap
78 78 def bundlebase(self, rev): return self.basemap[rev]
79 79 def chunk(self, rev, df=None, cachelen=4096):
80 80 # Warning: in case of bundle, the diff is against bundlebase,
81 81 # not against rev - 1
82 82 # XXX: could use some caching
83 83 if not self.bundle(rev):
84 84 return revlog.revlog.chunk(self, rev, df, cachelen)
85 85 self.bundlefile.seek(self.start(rev))
86 86 return self.bundlefile.read(self.length(rev))
87 87
88 88 def revdiff(self, rev1, rev2):
89 89 """return or calculate a delta between two revisions"""
90 90 if self.bundle(rev1) and self.bundle(rev2):
91 91 # hot path for bundle
92 92 revb = self.rev(self.bundlebase(rev2))
93 93 if revb == rev1:
94 94 return self.chunk(rev2)
95 95 elif not self.bundle(rev1) and not self.bundle(rev2):
96 96 return revlog.revlog.chunk(self, rev1, rev2)
97 97
98 98 return self.diff(self.revision(self.node(rev1)),
99 99 self.revision(self.node(rev2)))
100 100
101 101 def revision(self, node):
102 102 """return an uncompressed revision of a given"""
103 103 if node == nullid: return ""
104 104
105 105 text = None
106 106 chain = []
107 107 iter_node = node
108 108 rev = self.rev(iter_node)
109 109 # reconstruct the revision if it is from a changegroup
110 110 while self.bundle(rev):
111 111 if self.cache and self.cache[0] == iter_node:
112 112 text = self.cache[2]
113 113 break
114 114 chain.append(rev)
115 115 iter_node = self.bundlebase(rev)
116 116 rev = self.rev(iter_node)
117 117 if text is None:
118 118 text = revlog.revlog.revision(self, iter_node)
119 119
120 120 while chain:
121 121 delta = self.chunk(chain.pop())
122 122 text = self.patches(text, [delta])
123 123
124 124 p1, p2 = self.parents(node)
125 125 if node != revlog.hash(text, p1, p2):
126 126 raise revlog.RevlogError(_("integrity check failed on %s:%d")
127 127 % (self.datafile, self.rev(node)))
128 128
129 129 self.cache = (node, self.rev(node), text)
130 130 return text
131 131
132 132 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
133 133 raise NotImplementedError
134 134 def addgroup(self, revs, linkmapper, transaction, unique=0):
135 135 raise NotImplementedError
136 136 def strip(self, rev, minlink):
137 137 raise NotImplementedError
138 138 def checksize(self):
139 139 raise NotImplementedError
140 140
141 141 class bundlechangelog(bundlerevlog, changelog.changelog):
142 142 def __init__(self, opener, bundlefile):
143 143 changelog.changelog.__init__(self, opener)
144 144 bundlerevlog.__init__(self, opener, "00changelog.i", "00changelog.d",
145 145 bundlefile)
146 146
147 147 class bundlemanifest(bundlerevlog, manifest.manifest):
148 148 def __init__(self, opener, bundlefile, linkmapper):
149 149 manifest.manifest.__init__(self, opener)
150 150 bundlerevlog.__init__(self, opener, self.indexfile, self.datafile,
151 151 bundlefile, linkmapper)
152 152
153 153 class bundlefilelog(bundlerevlog, filelog.filelog):
154 154 def __init__(self, opener, path, bundlefile, linkmapper):
155 155 filelog.filelog.__init__(self, opener, path)
156 156 bundlerevlog.__init__(self, opener, self.indexfile, self.datafile,
157 157 bundlefile, linkmapper)
158 158
159 159 class bundlerepository(localrepo.localrepository):
160 160 def __init__(self, ui, path, bundlename):
161 161 localrepo.localrepository.__init__(self, ui, path)
162
163 self._url = 'bundle:' + bundlename
164 if path: self._url += '+' + path
165
162 166 self.tempfile = None
163 167 self.bundlefile = open(bundlename, "rb")
164 168 header = self.bundlefile.read(6)
165 169 if not header.startswith("HG"):
166 170 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
167 171 elif not header.startswith("HG10"):
168 172 raise util.Abort(_("%s: unknown bundle version") % bundlename)
169 173 elif header == "HG10BZ":
170 174 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
171 175 suffix=".hg10un", dir=self.path)
172 176 self.tempfile = temp
173 177 fptemp = os.fdopen(fdtemp, 'wb')
174 178 def generator(f):
175 179 zd = bz2.BZ2Decompressor()
176 180 zd.decompress("BZ")
177 181 for chunk in f:
178 182 yield zd.decompress(chunk)
179 183 gen = generator(util.filechunkiter(self.bundlefile, 4096))
180 184
181 185 try:
182 186 fptemp.write("HG10UN")
183 187 for chunk in gen:
184 188 fptemp.write(chunk)
185 189 finally:
186 190 fptemp.close()
187 191 self.bundlefile.close()
188 192
189 193 self.bundlefile = open(self.tempfile, "rb")
190 194 # seek right after the header
191 195 self.bundlefile.seek(6)
192 196 elif header == "HG10UN":
193 197 # nothing to do
194 198 pass
195 199 else:
196 200 raise util.Abort(_("%s: unknown bundle compression type")
197 201 % bundlename)
198 202 self.changelog = bundlechangelog(self.opener, self.bundlefile)
199 203 self.manifest = bundlemanifest(self.opener, self.bundlefile,
200 204 self.changelog.rev)
201 205 # dict with the mapping 'filename' -> position in the bundle
202 206 self.bundlefilespos = {}
203 207 while 1:
204 208 f = changegroup.getchunk(self.bundlefile)
205 209 if not f:
206 210 break
207 211 self.bundlefilespos[f] = self.bundlefile.tell()
208 212 for c in changegroup.chunkiter(self.bundlefile):
209 213 pass
210 214
215 def url(self):
216 return self._url
217
211 218 def dev(self):
212 219 return -1
213 220
214 221 def file(self, f):
215 222 if f[0] == '/':
216 223 f = f[1:]
217 224 if f in self.bundlefilespos:
218 225 self.bundlefile.seek(self.bundlefilespos[f])
219 226 return bundlefilelog(self.opener, f, self.bundlefile,
220 227 self.changelog.rev)
221 228 else:
222 229 return filelog.filelog(self.opener, f)
223 230
224 231 def close(self):
225 232 """Close assigned bundle file immediately."""
226 233 self.bundlefile.close()
227 234
228 235 def __del__(self):
229 236 if not self.bundlefile.closed:
230 237 self.bundlefile.close()
231 238 if self.tempfile is not None:
232 239 os.unlink(self.tempfile)
240
241 def instance(ui, path, create):
242 if create:
243 raise util.Abort(_('cannot create new bundle repository'))
244 path = util.drop_scheme('file', path)
245 if path.startswith('bundle:'):
246 path = util.drop_scheme('bundle', path)
247 s = path.split("+", 1)
248 if len(s) == 1:
249 repopath, bundlename = "", s[0]
250 else:
251 repopath, bundlename = s
252 else:
253 repopath, bundlename = '', path
254 return bundlerepository(ui, repopath, bundlename)
@@ -1,48 +1,48
1 1 # changelog.py - changelog class for mercurial
2 2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from revlog import *
9 9 from i18n import gettext as _
10 10 from demandload import demandload
11 11 demandload(globals(), "os time util")
12 12
13 13 class changelog(revlog):
14 14 def __init__(self, opener, defversion=REVLOGV0):
15 15 revlog.__init__(self, opener, "00changelog.i", "00changelog.d",
16 16 defversion)
17 17
18 18 def extract(self, text):
19 19 if not text:
20 20 return (nullid, "", (0, 0), [], "")
21 21 last = text.index("\n\n")
22 22 desc = text[last + 2:]
23 23 l = text[:last].splitlines()
24 24 manifest = bin(l[0])
25 25 user = l[1]
26 26 date = l[2].split(' ')
27 27 time = float(date.pop(0))
28 28 try:
29 29 # various tools did silly things with the time zone field.
30 30 timezone = int(date[0])
31 31 except:
32 32 timezone = 0
33 33 files = l[3:]
34 34 return (manifest, user, (time, timezone), files, desc)
35 35
36 36 def read(self, node):
37 37 return self.extract(self.revision(node))
38 38
39 39 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
40 40 user=None, date=None):
41 41 if date:
42 42 parseddate = "%d %d" % util.parsedate(date)
43 43 else:
44 44 parseddate = "%d %d" % util.makedate()
45 45 list.sort()
46 46 l = [hex(manifest), user, parseddate] + list + ["", desc]
47 47 text = "\n".join(l)
48 48 return self.addrevision(text, transaction, self.count(), p1, p2)
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now