##// END OF EJS Templates
Transparent proxy support...
mpm@selenic.com -
r321:73b8a8a0 default
parent child Browse files
Show More
@@ -1,212 +1,228 b''
1 HG(1)
1 HG(1)
2 =====
2 =====
3 Matt Mackall <mpm@selenic.com>
3 Matt Mackall <mpm@selenic.com>
4 v0.5, 27 May 2005
4 v0.5, 27 May 2005
5
5
6 NAME
6 NAME
7 ----
7 ----
8 hg - command line interface to the Mercurial source code management system
8 hg - command line interface to the Mercurial source code management system
9
9
10 SYNOPSIS
10 SYNOPSIS
11 --------
11 --------
12 'hg' [-v -d -q -y] <command> [command options] [files]
12 'hg' [-v -d -q -y] <command> [command options] [files]
13
13
14 DESCRIPTION
14 DESCRIPTION
15 -----------
15 -----------
16 The hg(1) command provides a command line interface to the Mercurial system.
16 The hg(1) command provides a command line interface to the Mercurial system.
17
17
18 NOTE
18 NOTE
19 ----
19 ----
20 Many of the hg commands are not yet subdirectory and/or working directory
20 Many of the hg commands are not yet subdirectory and/or working directory
21 aware. This means that some commands will only work in the top level
21 aware. This means that some commands will only work in the top level
22 repository directory or will only accept paths and filenames relative to the
22 repository directory or will only accept paths and filenames relative to the
23 top level. Merges and commits, in particular, should be done in the
23 top level. Merges and commits, in particular, should be done in the
24 top-level directory.
24 top-level directory.
25
25
26 OPTIONS
26 OPTIONS
27 -------
27 -------
28 --debug, -d::
28 --debug, -d::
29 enable debugging output
29 enable debugging output
30
30
31 --quiet, -q::
31 --quiet, -q::
32 suppress output
32 suppress output
33
33
34 --verbose, -v::
34 --verbose, -v::
35 enable additional output
35 enable additional output
36
36
37 --noninteractive, -y::
37 --noninteractive, -y::
38 do not prompt, assume 'yes' for any required answers
38 do not prompt, assume 'yes' for any required answers
39
39
40 COMMAND ELEMENTS
40 COMMAND ELEMENTS
41 ----------------
41 ----------------
42
42
43 files ...::
43 files ...::
44 indicates one or more filename or relative path filenames
44 indicates one or more filename or relative path filenames
45
45
46 path::
46 path::
47 indicates a path on the local machine
47 indicates a path on the local machine
48
48
49 revision::
49 revision::
50 indicates a changeset which can be specified as a changeset id (int),
50 indicates a changeset which can be specified as a changeset id (int),
51 a tag, or a unique substring of the changeset hash value
51 a tag, or a unique substring of the changeset hash value
52
52
53 repository path::
53 repository path::
54 is either the pathname of a local repository of the URI of a remote
54 is either the pathname of a local repository of the URI of a remote
55 repository. There are two available URI protocols, http:// which is
55 repository. There are two available URI protocols, http:// which is
56 fast and the old-http:// protocol which is much slower but does not
56 fast and the old-http:// protocol which is much slower but does not
57 require python on the web host.
57 require python on the web host.
58
58
59 COMMANDS
59 COMMANDS
60 --------
60 --------
61 add [files ...]::
61 add [files ...]::
62 add the given files to the repository. Note that this just schedules the
62 add the given files to the repository. Note that this just schedules the
63 files for addition at the next hg commit time.
63 files for addition at the next hg commit time.
64
64
65 addremove::
65 addremove::
66 add all new files and remove all missing files from the repository. new
66 add all new files and remove all missing files from the repository. new
67 files are ignored if they match any of the patterns in .hgignore
67 files are ignored if they match any of the patterns in .hgignore
68
68
69 annotate [-r revision -u -n -c] [files ...]::
69 annotate [-r revision -u -n -c] [files ...]::
70 list the files with each line showing the revision id responsible
70 list the files with each line showing the revision id responsible
71 for that line. -u will add the author to the revision id, -c will
71 for that line. -u will add the author to the revision id, -c will
72 print the changeset hash, and -n will ...
72 print the changeset hash, and -n will ...
73
73
74 branch <path>::
74 branch <path>::
75 create a new branch of the repository indicated by path in the current
75 create a new branch of the repository indicated by path in the current
76 directory. Note that there should not be a repository already initialized
76 directory. Note that there should not be a repository already initialized
77 in the current directory
77 in the current directory
78
78
79 checkout [revision]::
79 checkout [revision]::
80 check out the indicated version of the repository into the working
80 check out the indicated version of the repository into the working
81 directory. Note that currently no merge occurs with changed files
81 directory. Note that currently no merge occurs with changed files
82 in the working dir.
82 in the working dir.
83
83
84 commit::
84 commit::
85 commit all changed files in the working dir to the repository. This uses
85 commit all changed files in the working dir to the repository. This uses
86 the EDITOR environment variable to bring up an editor to add a commit
86 the EDITOR environment variable to bring up an editor to add a commit
87 comment.
87 comment.
88
88
89 diff [-r revision] [-r revision] [files ...]::
89 diff [-r revision] [-r revision] [files ...]::
90 generate a unified diff of the indicated files. If there are no
90 generate a unified diff of the indicated files. If there are no
91 revisions specified, the working directory file is compared to
91 revisions specified, the working directory file is compared to
92 the tip, one revision specified indicates a comparison between the
92 the tip, one revision specified indicates a comparison between the
93 working directory file and the specified revision, two revisions
93 working directory file and the specified revision, two revisions
94 compares the two versions specified.
94 compares the two versions specified.
95
95
96 dump <file> [revision]::
96 dump <file> [revision]::
97 print the indicated revision of the file
97 print the indicated revision of the file
98
98
99 dumpmanifest [revision]::
99 dumpmanifest [revision]::
100 print the indicated revision of the manifest (list of version controlled
100 print the indicated revision of the manifest (list of version controlled
101 files)
101 files)
102
102
103 export [revision]::
103 export [revision]::
104 print the changeset header (author, changeset hash, parent, and commit
104 print the changeset header (author, changeset hash, parent, and commit
105 comment) and the diffs for a particular revision.
105 comment) and the diffs for a particular revision.
106
106
107 history::
107 history::
108 print the revision history of the repository
108 print the revision history of the repository
109
109
110 init::
110 init::
111 initialize a repository in the current directory
111 initialize a repository in the current directory
112
112
113 log <file>::
113 log <file>::
114 print the revision history of the specified file
114 print the revision history of the specified file
115
115
116 merge <repository path>::
116 merge <repository path>::
117 pull any changes from the specified repository to the repository in the
117 pull any changes from the specified repository to the repository in the
118 current directory. Use the value of the HGMERGE environment variable
118 current directory. Use the value of the HGMERGE environment variable
119 as a program to resolve any merge conflicts between the two repositories.
119 as a program to resolve any merge conflicts between the two repositories.
120 An implicit commit is done at the end of this process if there were any
120 An implicit commit is done at the end of this process if there were any
121 merge conflicts. Note that merge does not yet merge with changed files
121 merge conflicts. Note that merge does not yet merge with changed files
122 in the working dir.
122 in the working dir.
123
123
124 recover::
124 recover::
125 rollback an interrupted transaction
125 rollback an interrupted transaction
126
126
127 remove [files ...]::
127 remove [files ...]::
128 schedule the indicated files for removal from the repository at the next
128 schedule the indicated files for removal from the repository at the next
129 commit
129 commit
130
130
131 serve [-a addr -n name -p port -t templatedir]::
131 serve [-a addr -n name -p port -t templatedir]::
132 this will start an http server, by default on port 8000, that will
132 this will start an http server, by default on port 8000, that will
133 allow browsing the repository using the hgweb interface and will allow
133 allow browsing the repository using the hgweb interface and will allow
134 merging from the repository. -a sets the interface address, -p the
134 merging from the repository. -a sets the interface address, -p the
135 port to listen on, -n the name of the repository and -t sets the
135 port to listen on, -n the name of the repository and -t sets the
136 location of the template directory.
136 location of the template directory.
137
137
138 status::
138 status::
139 list new, changed, and missing files in the working directory
139 list new, changed, and missing files in the working directory
140
140
141 tags::
141 tags::
142 list the current tags
142 list the current tags
143
143
144 undo::
144 undo::
145 undo the last transaction
145 undo the last transaction
146
146
147 ENVIRONMENT VARIABLES
147 ENVIRONMENT VARIABLES
148 ---------------------
148 ---------------------
149 HGMERGE::
149 HGMERGE::
150 points to an executable to use for resolving merge conflicts, the
150 points to an executable to use for resolving merge conflicts, the
151 program will be executed with four arguments: local file, remote
151 program will be executed with four arguments: local file, remote
152 file, ancestor file, and original filename.
152 file, ancestor file, and original filename.
153
153
154 HGUSER::
154 HGUSER::
155 this is the string used for the author value of a commit
155 this is the string used for the author value of a commit
156
156
157 HG_OPTS::
157 HG_OPTS::
158 this string is used for default arguments to hg
158 this string is used for default arguments to hg
159
159
160 PYTHONPATH::
160 PYTHONPATH::
161 this is used by Python to find imported modules and needs to be set
161 this is used by Python to find imported modules and needs to be set
162 appropriately based on where mercurial is installed
162 appropriately based on where mercurial is installed
163
163
164 EMAIL::
164 EMAIL::
165 if HGUSER is not set, this will be used next as the author value for
165 if HGUSER is not set, this will be used next as the author value for
166 a commit
166 a commit
167
167
168 LOGNAME::
168 LOGNAME::
169 if neither HGUSER nor EMAIL is set, LOGNAME will be used (with
169 if neither HGUSER nor EMAIL is set, LOGNAME will be used (with
170 '@hostname' appended) as the author value for a commit
170 '@hostname' appended) as the author value for a commit
171
171
172 EDITOR::
172 EDITOR::
173 this is the name of the editor to use when committing
173 this is the name of the editor to use when committing
174
174
175 FILES
175 FILES
176 -----
176 -----
177 .hgignore::
177 .hgignore::
178 this file contains regular expressions (one per line) that describe file
178 this file contains regular expressions (one per line) that describe file
179 names that should be ignored by hg
179 names that should be ignored by hg
180
180
181 .hgtags::
181 .hgtags::
182 this file contains changeset hash values and text tag names (one of each
182 this file contains changeset hash values and text tag names (one of each
183 seperated by spaces) that correspond to tagged versions of the repository
183 seperated by spaces) that correspond to tagged versions of the repository
184 contents.
184 contents.
185
185
186 $HOME/.hgpaths::
186 $HOME/.hgpaths::
187 this file contains a mapping from a symbolic name to a repository path
187 this file contains a mapping from a symbolic name to a repository path
188 (which could be a local path or a remote URI), the format is
188 (which could be a local path or a remote URI), the format is
189 <symbolic name> <repository path> with each mapping on a seperate line
189 <symbolic name> <repository path> with each mapping on a seperate line
190
190
191 NON_TRANSPARENT PROXY SUPPORT
192 -----
193
194 To access a mercurial repository through a proxy,
195 create a file $HOME/.hgrc in the following format:
196
197 [http_proxy]
198 host=myproxy:8080
199 user=<username>
200 passwd=<password>
201 no=<localhost1>,<localhost2>,<localhost3>,...
202
203 "user","passwd" fields are used for authenticating proxies,
204 "no" is a comma-separated list of local host names
205 for which proxy must be bypassed.
206
191 BUGS
207 BUGS
192 ----
208 ----
193 Probably lots, please post them to the mailing list (See Resources below)
209 Probably lots, please post them to the mailing list (See Resources below)
194 when you find them.
210 when you find them.
195
211
196 AUTHOR
212 AUTHOR
197 ------
213 ------
198 Written by Matt Mackall <mpm@selenic.com>
214 Written by Matt Mackall <mpm@selenic.com>
199
215
200 RESOURCES
216 RESOURCES
201 ---------
217 ---------
202 http://selenic.com/mercurial[Main Web Site]
218 http://selenic.com/mercurial[Main Web Site]
203
219
204 http://selenic.com/hg[Source code repository]
220 http://selenic.com/hg[Source code repository]
205
221
206 http://selenic.com/mailman/listinfo/mercurial[Mailing list]
222 http://selenic.com/mailman/listinfo/mercurial[Mailing list]
207
223
208 COPYING
224 COPYING
209 -------
225 -------
210 Copyright (C) 2005 Matt Mackall.
226 Copyright (C) 2005 Matt Mackall.
211 Free use of this software is granted under the terms of the GNU General
227 Free use of this software is granted under the terms of the GNU General
212 Public License (GPL).
228 Public License (GPL).
@@ -1,1306 +1,1336 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import sys, struct, os
8 import sys, struct, os
9 from revlog import *
9 from revlog import *
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "re lock urllib urllib2 transaction time socket")
11 demandload(globals(), "re lock urllib urllib2 transaction time socket")
12 demandload(globals(), "tempfile byterange difflib")
12 demandload(globals(), "tempfile byterange difflib")
13
13
14 def is_exec(f):
14 def is_exec(f):
15 return (os.stat(f).st_mode & 0100 != 0)
15 return (os.stat(f).st_mode & 0100 != 0)
16
16
17 def set_exec(f, mode):
17 def set_exec(f, mode):
18 s = os.stat(f).st_mode
18 s = os.stat(f).st_mode
19 if (s & 0100 != 0) == mode:
19 if (s & 0100 != 0) == mode:
20 return
20 return
21 if mode:
21 if mode:
22 # Turn on +x for every +r bit when making a file executable
22 # Turn on +x for every +r bit when making a file executable
23 # and obey umask.
23 # and obey umask.
24 umask = os.umask(0)
24 umask = os.umask(0)
25 os.umask(umask)
25 os.umask(umask)
26 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
26 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
27 else:
27 else:
28 os.chmod(f, s & 0666)
28 os.chmod(f, s & 0666)
29
29
30 class filelog(revlog):
30 class filelog(revlog):
31 def __init__(self, opener, path):
31 def __init__(self, opener, path):
32 revlog.__init__(self, opener,
32 revlog.__init__(self, opener,
33 os.path.join("data", path + ".i"),
33 os.path.join("data", path + ".i"),
34 os.path.join("data", path + ".d"))
34 os.path.join("data", path + ".d"))
35
35
36 def read(self, node):
36 def read(self, node):
37 return self.revision(node)
37 return self.revision(node)
38 def add(self, text, transaction, link, p1=None, p2=None):
38 def add(self, text, transaction, link, p1=None, p2=None):
39 return self.addrevision(text, transaction, link, p1, p2)
39 return self.addrevision(text, transaction, link, p1, p2)
40
40
41 def annotate(self, node):
41 def annotate(self, node):
42
42
43 def decorate(text, rev):
43 def decorate(text, rev):
44 return [(rev, l) for l in text.splitlines(1)]
44 return [(rev, l) for l in text.splitlines(1)]
45
45
46 def strip(annotation):
46 def strip(annotation):
47 return [e[1] for e in annotation]
47 return [e[1] for e in annotation]
48
48
49 def pair(parent, child):
49 def pair(parent, child):
50 new = []
50 new = []
51 sm = difflib.SequenceMatcher(None, strip(parent), strip(child))
51 sm = difflib.SequenceMatcher(None, strip(parent), strip(child))
52 for o, m, n, s, t in sm.get_opcodes():
52 for o, m, n, s, t in sm.get_opcodes():
53 if o == 'equal':
53 if o == 'equal':
54 new += parent[m:n]
54 new += parent[m:n]
55 else:
55 else:
56 new += child[s:t]
56 new += child[s:t]
57 return new
57 return new
58
58
59 # find all ancestors
59 # find all ancestors
60 needed = {node:1}
60 needed = {node:1}
61 visit = [node]
61 visit = [node]
62 while visit:
62 while visit:
63 n = visit.pop(0)
63 n = visit.pop(0)
64 for p in self.parents(n):
64 for p in self.parents(n):
65 if p not in needed:
65 if p not in needed:
66 needed[p] = 1
66 needed[p] = 1
67 visit.append(p)
67 visit.append(p)
68 else:
68 else:
69 # count how many times we'll use this
69 # count how many times we'll use this
70 needed[p] += 1
70 needed[p] += 1
71
71
72 # sort by revision which is a topological order
72 # sort by revision which is a topological order
73 visit = needed.keys()
73 visit = needed.keys()
74 visit = [ (self.rev(n), n) for n in visit ]
74 visit = [ (self.rev(n), n) for n in visit ]
75 visit.sort()
75 visit.sort()
76 visit = [ p[1] for p in visit ]
76 visit = [ p[1] for p in visit ]
77 hist = {}
77 hist = {}
78
78
79 for n in visit:
79 for n in visit:
80 curr = decorate(self.read(n), self.linkrev(n))
80 curr = decorate(self.read(n), self.linkrev(n))
81 for p in self.parents(n):
81 for p in self.parents(n):
82 if p != nullid:
82 if p != nullid:
83 curr = pair(hist[p], curr)
83 curr = pair(hist[p], curr)
84 # trim the history of unneeded revs
84 # trim the history of unneeded revs
85 needed[p] -= 1
85 needed[p] -= 1
86 if not needed[p]:
86 if not needed[p]:
87 del hist[p]
87 del hist[p]
88 hist[n] = curr
88 hist[n] = curr
89
89
90 return hist[n]
90 return hist[n]
91
91
92 class manifest(revlog):
92 class manifest(revlog):
93 def __init__(self, opener):
93 def __init__(self, opener):
94 self.mapcache = None
94 self.mapcache = None
95 self.listcache = None
95 self.listcache = None
96 self.addlist = None
96 self.addlist = None
97 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
97 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
98
98
99 def read(self, node):
99 def read(self, node):
100 if node == nullid: return {} # don't upset local cache
100 if node == nullid: return {} # don't upset local cache
101 if self.mapcache and self.mapcache[0] == node:
101 if self.mapcache and self.mapcache[0] == node:
102 return self.mapcache[1].copy()
102 return self.mapcache[1].copy()
103 text = self.revision(node)
103 text = self.revision(node)
104 map = {}
104 map = {}
105 flag = {}
105 flag = {}
106 self.listcache = (text, text.splitlines(1))
106 self.listcache = (text, text.splitlines(1))
107 for l in self.listcache[1]:
107 for l in self.listcache[1]:
108 (f, n) = l.split('\0')
108 (f, n) = l.split('\0')
109 map[f] = bin(n[:40])
109 map[f] = bin(n[:40])
110 flag[f] = (n[40:-1] == "x")
110 flag[f] = (n[40:-1] == "x")
111 self.mapcache = (node, map, flag)
111 self.mapcache = (node, map, flag)
112 return map
112 return map
113
113
114 def readflags(self, node):
114 def readflags(self, node):
115 if node == nullid: return {} # don't upset local cache
115 if node == nullid: return {} # don't upset local cache
116 if self.mapcache or self.mapcache[0] != node:
116 if self.mapcache or self.mapcache[0] != node:
117 self.read(node)
117 self.read(node)
118 return self.mapcache[2]
118 return self.mapcache[2]
119
119
120 def diff(self, a, b):
120 def diff(self, a, b):
121 # this is sneaky, as we're not actually using a and b
121 # this is sneaky, as we're not actually using a and b
122 if self.listcache and self.addlist and self.listcache[0] == a:
122 if self.listcache and self.addlist and self.listcache[0] == a:
123 d = mdiff.diff(self.listcache[1], self.addlist, 1)
123 d = mdiff.diff(self.listcache[1], self.addlist, 1)
124 if mdiff.patch(a, d) != b:
124 if mdiff.patch(a, d) != b:
125 sys.stderr.write("*** sortdiff failed, falling back ***\n")
125 sys.stderr.write("*** sortdiff failed, falling back ***\n")
126 return mdiff.textdiff(a, b)
126 return mdiff.textdiff(a, b)
127 return d
127 return d
128 else:
128 else:
129 return mdiff.textdiff(a, b)
129 return mdiff.textdiff(a, b)
130
130
131 def add(self, map, flags, transaction, link, p1=None, p2=None):
131 def add(self, map, flags, transaction, link, p1=None, p2=None):
132 files = map.keys()
132 files = map.keys()
133 files.sort()
133 files.sort()
134
134
135 self.addlist = ["%s\000%s%s\n" %
135 self.addlist = ["%s\000%s%s\n" %
136 (f, hex(map[f]), flags[f] and "x" or '')
136 (f, hex(map[f]), flags[f] and "x" or '')
137 for f in files]
137 for f in files]
138 text = "".join(self.addlist)
138 text = "".join(self.addlist)
139
139
140 n = self.addrevision(text, transaction, link, p1, p2)
140 n = self.addrevision(text, transaction, link, p1, p2)
141 self.mapcache = (n, map, flags)
141 self.mapcache = (n, map, flags)
142 self.listcache = (text, self.addlist)
142 self.listcache = (text, self.addlist)
143 self.addlist = None
143 self.addlist = None
144
144
145 return n
145 return n
146
146
147 class changelog(revlog):
147 class changelog(revlog):
148 def __init__(self, opener):
148 def __init__(self, opener):
149 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
149 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
150
150
151 def extract(self, text):
151 def extract(self, text):
152 if not text:
152 if not text:
153 return (nullid, "", "0", [], "")
153 return (nullid, "", "0", [], "")
154 last = text.index("\n\n")
154 last = text.index("\n\n")
155 desc = text[last + 2:]
155 desc = text[last + 2:]
156 l = text[:last].splitlines()
156 l = text[:last].splitlines()
157 manifest = bin(l[0])
157 manifest = bin(l[0])
158 user = l[1]
158 user = l[1]
159 date = l[2]
159 date = l[2]
160 files = l[3:]
160 files = l[3:]
161 return (manifest, user, date, files, desc)
161 return (manifest, user, date, files, desc)
162
162
163 def read(self, node):
163 def read(self, node):
164 return self.extract(self.revision(node))
164 return self.extract(self.revision(node))
165
165
166 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
166 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
167 user=None, date=None):
167 user=None, date=None):
168 user = (user or
168 user = (user or
169 os.environ.get("HGUSER") or
169 os.environ.get("HGUSER") or
170 os.environ.get("EMAIL") or
170 os.environ.get("EMAIL") or
171 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
171 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
172 date = date or "%d %d" % (time.time(), time.timezone)
172 date = date or "%d %d" % (time.time(), time.timezone)
173 list.sort()
173 list.sort()
174 l = [hex(manifest), user, date] + list + ["", desc]
174 l = [hex(manifest), user, date] + list + ["", desc]
175 text = "\n".join(l)
175 text = "\n".join(l)
176 return self.addrevision(text, transaction, self.count(), p1, p2)
176 return self.addrevision(text, transaction, self.count(), p1, p2)
177
177
178 class dirstate:
178 class dirstate:
179 def __init__(self, opener, ui, root):
179 def __init__(self, opener, ui, root):
180 self.opener = opener
180 self.opener = opener
181 self.root = root
181 self.root = root
182 self.dirty = 0
182 self.dirty = 0
183 self.ui = ui
183 self.ui = ui
184 self.map = None
184 self.map = None
185 self.pl = None
185 self.pl = None
186
186
187 def __del__(self):
187 def __del__(self):
188 if self.dirty:
188 if self.dirty:
189 self.write()
189 self.write()
190
190
191 def __getitem__(self, key):
191 def __getitem__(self, key):
192 try:
192 try:
193 return self.map[key]
193 return self.map[key]
194 except TypeError:
194 except TypeError:
195 self.read()
195 self.read()
196 return self[key]
196 return self[key]
197
197
198 def __contains__(self, key):
198 def __contains__(self, key):
199 if not self.map: self.read()
199 if not self.map: self.read()
200 return key in self.map
200 return key in self.map
201
201
202 def parents(self):
202 def parents(self):
203 if not self.pl:
203 if not self.pl:
204 self.read()
204 self.read()
205 return self.pl
205 return self.pl
206
206
207 def setparents(self, p1, p2 = nullid):
207 def setparents(self, p1, p2 = nullid):
208 self.dirty = 1
208 self.dirty = 1
209 self.pl = p1, p2
209 self.pl = p1, p2
210
210
211 def state(self, key):
211 def state(self, key):
212 try:
212 try:
213 return self[key][0]
213 return self[key][0]
214 except KeyError:
214 except KeyError:
215 return "?"
215 return "?"
216
216
217 def read(self):
217 def read(self):
218 if self.map is not None: return self.map
218 if self.map is not None: return self.map
219
219
220 self.map = {}
220 self.map = {}
221 self.pl = [nullid, nullid]
221 self.pl = [nullid, nullid]
222 try:
222 try:
223 st = self.opener("dirstate").read()
223 st = self.opener("dirstate").read()
224 if not st: return
224 if not st: return
225 except: return
225 except: return
226
226
227 self.pl = [st[:20], st[20: 40]]
227 self.pl = [st[:20], st[20: 40]]
228
228
229 pos = 40
229 pos = 40
230 while pos < len(st):
230 while pos < len(st):
231 e = struct.unpack(">cllll", st[pos:pos+17])
231 e = struct.unpack(">cllll", st[pos:pos+17])
232 l = e[4]
232 l = e[4]
233 pos += 17
233 pos += 17
234 f = st[pos:pos + l]
234 f = st[pos:pos + l]
235 self.map[f] = e[:4]
235 self.map[f] = e[:4]
236 pos += l
236 pos += l
237
237
238 def update(self, files, state):
238 def update(self, files, state):
239 ''' current states:
239 ''' current states:
240 n normal
240 n normal
241 m needs merging
241 m needs merging
242 r marked for removal
242 r marked for removal
243 a marked for addition'''
243 a marked for addition'''
244
244
245 if not files: return
245 if not files: return
246 self.read()
246 self.read()
247 self.dirty = 1
247 self.dirty = 1
248 for f in files:
248 for f in files:
249 if state == "r":
249 if state == "r":
250 self.map[f] = ('r', 0, 0, 0)
250 self.map[f] = ('r', 0, 0, 0)
251 else:
251 else:
252 s = os.stat(os.path.join(self.root, f))
252 s = os.stat(os.path.join(self.root, f))
253 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
253 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
254
254
255 def forget(self, files):
255 def forget(self, files):
256 if not files: return
256 if not files: return
257 self.read()
257 self.read()
258 self.dirty = 1
258 self.dirty = 1
259 for f in files:
259 for f in files:
260 try:
260 try:
261 del self.map[f]
261 del self.map[f]
262 except KeyError:
262 except KeyError:
263 self.ui.warn("not in dirstate: %s!\n" % f)
263 self.ui.warn("not in dirstate: %s!\n" % f)
264 pass
264 pass
265
265
266 def clear(self):
266 def clear(self):
267 self.map = {}
267 self.map = {}
268 self.dirty = 1
268 self.dirty = 1
269
269
270 def write(self):
270 def write(self):
271 st = self.opener("dirstate", "w")
271 st = self.opener("dirstate", "w")
272 st.write("".join(self.pl))
272 st.write("".join(self.pl))
273 for f, e in self.map.items():
273 for f, e in self.map.items():
274 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
274 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
275 st.write(e + f)
275 st.write(e + f)
276 self.dirty = 0
276 self.dirty = 0
277
277
278 def copy(self):
278 def copy(self):
279 self.read()
279 self.read()
280 return self.map.copy()
280 return self.map.copy()
281
281
282 # used to avoid circular references so destructors work
282 # used to avoid circular references so destructors work
283 def opener(base):
283 def opener(base):
284 p = base
284 p = base
285 def o(path, mode="r"):
285 def o(path, mode="r"):
286 if p[:7] == "http://":
286 if p[:7] == "http://":
287 f = os.path.join(p, urllib.quote(path))
287 f = os.path.join(p, urllib.quote(path))
288 return httprangereader(f)
288 return httprangereader(f)
289
289
290 f = os.path.join(p, path)
290 f = os.path.join(p, path)
291
291
292 mode += "b" # for that other OS
292 mode += "b" # for that other OS
293
293
294 if mode[0] != "r":
294 if mode[0] != "r":
295 try:
295 try:
296 s = os.stat(f)
296 s = os.stat(f)
297 except OSError:
297 except OSError:
298 d = os.path.dirname(f)
298 d = os.path.dirname(f)
299 if not os.path.isdir(d):
299 if not os.path.isdir(d):
300 os.makedirs(d)
300 os.makedirs(d)
301 else:
301 else:
302 if s.st_nlink > 1:
302 if s.st_nlink > 1:
303 file(f + ".tmp", "w").write(file(f).read())
303 file(f + ".tmp", "w").write(file(f).read())
304 os.rename(f+".tmp", f)
304 os.rename(f+".tmp", f)
305
305
306 return file(f, mode)
306 return file(f, mode)
307
307
308 return o
308 return o
309
309
310 class localrepository:
310 class localrepository:
311 def __init__(self, ui, path=None, create=0):
311 def __init__(self, ui, path=None, create=0):
312 self.remote = 0
312 self.remote = 0
313 if path and path[:7] == "http://":
313 if path and path[:7] == "http://":
314 self.remote = 1
314 self.remote = 1
315 self.path = path
315 self.path = path
316 else:
316 else:
317 if not path:
317 if not path:
318 p = os.getcwd()
318 p = os.getcwd()
319 while not os.path.isdir(os.path.join(p, ".hg")):
319 while not os.path.isdir(os.path.join(p, ".hg")):
320 p = os.path.dirname(p)
320 p = os.path.dirname(p)
321 if p == "/": raise "No repo found"
321 if p == "/": raise "No repo found"
322 path = p
322 path = p
323 self.path = os.path.join(path, ".hg")
323 self.path = os.path.join(path, ".hg")
324
324
325 self.root = path
325 self.root = path
326 self.ui = ui
326 self.ui = ui
327
327
328 if create:
328 if create:
329 os.mkdir(self.path)
329 os.mkdir(self.path)
330 os.mkdir(self.join("data"))
330 os.mkdir(self.join("data"))
331
331
332 self.opener = opener(self.path)
332 self.opener = opener(self.path)
333 self.wopener = opener(self.root)
333 self.wopener = opener(self.root)
334 self.manifest = manifest(self.opener)
334 self.manifest = manifest(self.opener)
335 self.changelog = changelog(self.opener)
335 self.changelog = changelog(self.opener)
336 self.ignorelist = None
336 self.ignorelist = None
337 self.tags = None
337 self.tags = None
338
338
339 if not self.remote:
339 if not self.remote:
340 self.dirstate = dirstate(self.opener, ui, self.root)
340 self.dirstate = dirstate(self.opener, ui, self.root)
341
341
342 def ignore(self, f):
342 def ignore(self, f):
343 if self.ignorelist is None:
343 if self.ignorelist is None:
344 self.ignorelist = []
344 self.ignorelist = []
345 try:
345 try:
346 l = self.wfile(".hgignore")
346 l = self.wfile(".hgignore")
347 for pat in l:
347 for pat in l:
348 if pat != "\n":
348 if pat != "\n":
349 self.ignorelist.append(re.compile(pat[:-1]))
349 self.ignorelist.append(re.compile(pat[:-1]))
350 except IOError: pass
350 except IOError: pass
351 for pat in self.ignorelist:
351 for pat in self.ignorelist:
352 if pat.search(f): return True
352 if pat.search(f): return True
353 return False
353 return False
354
354
355 def lookup(self, key):
355 def lookup(self, key):
356 if self.tags is None:
356 if self.tags is None:
357 self.tags = {}
357 self.tags = {}
358 try:
358 try:
359 # read each head of the tags file, ending with the tip
359 # read each head of the tags file, ending with the tip
360 # and add each tag found to the map, with "newer" ones
360 # and add each tag found to the map, with "newer" ones
361 # taking precedence
361 # taking precedence
362 fl = self.file(".hgtags")
362 fl = self.file(".hgtags")
363 h = fl.heads()
363 h = fl.heads()
364 h.reverse()
364 h.reverse()
365 for r in h:
365 for r in h:
366 for l in fl.revision(r).splitlines():
366 for l in fl.revision(r).splitlines():
367 if l:
367 if l:
368 n, k = l.split(" ")
368 n, k = l.split(" ")
369 self.tags[k] = bin(n)
369 self.tags[k] = bin(n)
370 except KeyError: pass
370 except KeyError: pass
371 self.tags['tip'] = self.changelog.tip()
371 self.tags['tip'] = self.changelog.tip()
372 try:
372 try:
373 return self.tags[key]
373 return self.tags[key]
374 except KeyError:
374 except KeyError:
375 return self.changelog.lookup(key)
375 return self.changelog.lookup(key)
376
376
377 def join(self, f):
377 def join(self, f):
378 return os.path.join(self.path, f)
378 return os.path.join(self.path, f)
379
379
380 def wjoin(self, f):
380 def wjoin(self, f):
381 return os.path.join(self.root, f)
381 return os.path.join(self.root, f)
382
382
383 def file(self, f):
383 def file(self, f):
384 if f[0] == '/': f = f[1:]
384 if f[0] == '/': f = f[1:]
385 return filelog(self.opener, f)
385 return filelog(self.opener, f)
386
386
387 def wfile(self, f, mode='r'):
387 def wfile(self, f, mode='r'):
388 return self.wopener(f, mode)
388 return self.wopener(f, mode)
389
389
390 def transaction(self):
390 def transaction(self):
391 # save dirstate for undo
391 # save dirstate for undo
392 try:
392 try:
393 ds = self.opener("dirstate").read()
393 ds = self.opener("dirstate").read()
394 except IOError:
394 except IOError:
395 ds = ""
395 ds = ""
396 self.opener("undo.dirstate", "w").write(ds)
396 self.opener("undo.dirstate", "w").write(ds)
397
397
398 return transaction.transaction(self.opener, self.join("journal"),
398 return transaction.transaction(self.opener, self.join("journal"),
399 self.join("undo"))
399 self.join("undo"))
400
400
401 def recover(self):
401 def recover(self):
402 lock = self.lock()
402 lock = self.lock()
403 if os.path.exists(self.join("recover")):
403 if os.path.exists(self.join("recover")):
404 self.ui.status("attempting to rollback interrupted transaction\n")
404 self.ui.status("attempting to rollback interrupted transaction\n")
405 return transaction.rollback(self.opener, self.join("recover"))
405 return transaction.rollback(self.opener, self.join("recover"))
406 else:
406 else:
407 self.ui.warn("no interrupted transaction available\n")
407 self.ui.warn("no interrupted transaction available\n")
408
408
409 def undo(self):
409 def undo(self):
410 lock = self.lock()
410 lock = self.lock()
411 if os.path.exists(self.join("undo")):
411 if os.path.exists(self.join("undo")):
412 self.ui.status("attempting to rollback last transaction\n")
412 self.ui.status("attempting to rollback last transaction\n")
413 transaction.rollback(self.opener, self.join("undo"))
413 transaction.rollback(self.opener, self.join("undo"))
414 self.dirstate = None
414 self.dirstate = None
415 os.rename(self.join("undo.dirstate"), self.join("dirstate"))
415 os.rename(self.join("undo.dirstate"), self.join("dirstate"))
416 self.dirstate = dirstate(self.opener, self.ui, self.root)
416 self.dirstate = dirstate(self.opener, self.ui, self.root)
417 else:
417 else:
418 self.ui.warn("no undo information available\n")
418 self.ui.warn("no undo information available\n")
419
419
420 def lock(self, wait = 1):
420 def lock(self, wait = 1):
421 try:
421 try:
422 return lock.lock(self.join("lock"), 0)
422 return lock.lock(self.join("lock"), 0)
423 except lock.LockHeld, inst:
423 except lock.LockHeld, inst:
424 if wait:
424 if wait:
425 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
425 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
426 return lock.lock(self.join("lock"), wait)
426 return lock.lock(self.join("lock"), wait)
427 raise inst
427 raise inst
428
428
429 def rawcommit(self, files, text, user, date, p1=None, p2=None):
429 def rawcommit(self, files, text, user, date, p1=None, p2=None):
430 p1 = p1 or self.dirstate.parents()[0] or nullid
430 p1 = p1 or self.dirstate.parents()[0] or nullid
431 p2 = p2 or self.dirstate.parents()[1] or nullid
431 p2 = p2 or self.dirstate.parents()[1] or nullid
432 c1 = self.changelog.read(p1)
432 c1 = self.changelog.read(p1)
433 c2 = self.changelog.read(p2)
433 c2 = self.changelog.read(p2)
434 m1 = self.manifest.read(c1[0])
434 m1 = self.manifest.read(c1[0])
435 mf1 = self.manifest.readflags(c1[0])
435 mf1 = self.manifest.readflags(c1[0])
436 m2 = self.manifest.read(c2[0])
436 m2 = self.manifest.read(c2[0])
437
437
438 tr = self.transaction()
438 tr = self.transaction()
439 mm = m1.copy()
439 mm = m1.copy()
440 mfm = mf1.copy()
440 mfm = mf1.copy()
441 linkrev = self.changelog.count()
441 linkrev = self.changelog.count()
442 self.dirstate.setparents(p1, p2)
442 self.dirstate.setparents(p1, p2)
443 for f in files:
443 for f in files:
444 try:
444 try:
445 t = self.wfile(f).read()
445 t = self.wfile(f).read()
446 tm = is_exec(self.wjoin(f))
446 tm = is_exec(self.wjoin(f))
447 r = self.file(f)
447 r = self.file(f)
448 mfm[f] = tm
448 mfm[f] = tm
449 mm[f] = r.add(t, tr, linkrev,
449 mm[f] = r.add(t, tr, linkrev,
450 m1.get(f, nullid), m2.get(f, nullid))
450 m1.get(f, nullid), m2.get(f, nullid))
451 self.dirstate.update([f], "n")
451 self.dirstate.update([f], "n")
452 except IOError:
452 except IOError:
453 try:
453 try:
454 del mm[f]
454 del mm[f]
455 del mfm[f]
455 del mfm[f]
456 self.dirstate.forget([f])
456 self.dirstate.forget([f])
457 except:
457 except:
458 # deleted from p2?
458 # deleted from p2?
459 pass
459 pass
460
460
461 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
461 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
462 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
462 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
463 tr.close()
463 tr.close()
464
464
465 def commit(self, files = None, text = "", user = None, date = None):
465 def commit(self, files = None, text = "", user = None, date = None):
466 commit = []
466 commit = []
467 remove = []
467 remove = []
468 if files:
468 if files:
469 for f in files:
469 for f in files:
470 s = self.dirstate.state(f)
470 s = self.dirstate.state(f)
471 if s in 'nmai':
471 if s in 'nmai':
472 commit.append(f)
472 commit.append(f)
473 elif s == 'r':
473 elif s == 'r':
474 remove.append(f)
474 remove.append(f)
475 else:
475 else:
476 self.ui.warn("%s not tracked!\n" % f)
476 self.ui.warn("%s not tracked!\n" % f)
477 else:
477 else:
478 (c, a, d, u) = self.diffdir(self.root)
478 (c, a, d, u) = self.diffdir(self.root)
479 commit = c + a
479 commit = c + a
480 remove = d
480 remove = d
481
481
482 if not commit and not remove:
482 if not commit and not remove:
483 self.ui.status("nothing changed\n")
483 self.ui.status("nothing changed\n")
484 return
484 return
485
485
486 p1, p2 = self.dirstate.parents()
486 p1, p2 = self.dirstate.parents()
487 c1 = self.changelog.read(p1)
487 c1 = self.changelog.read(p1)
488 c2 = self.changelog.read(p2)
488 c2 = self.changelog.read(p2)
489 m1 = self.manifest.read(c1[0])
489 m1 = self.manifest.read(c1[0])
490 mf1 = self.manifest.readflags(c1[0])
490 mf1 = self.manifest.readflags(c1[0])
491 m2 = self.manifest.read(c2[0])
491 m2 = self.manifest.read(c2[0])
492 lock = self.lock()
492 lock = self.lock()
493 tr = self.transaction()
493 tr = self.transaction()
494
494
495 # check in files
495 # check in files
496 new = {}
496 new = {}
497 linkrev = self.changelog.count()
497 linkrev = self.changelog.count()
498 commit.sort()
498 commit.sort()
499 for f in commit:
499 for f in commit:
500 self.ui.note(f + "\n")
500 self.ui.note(f + "\n")
501 try:
501 try:
502 fp = self.wjoin(f)
502 fp = self.wjoin(f)
503 mf1[f] = is_exec(fp)
503 mf1[f] = is_exec(fp)
504 t = file(fp).read()
504 t = file(fp).read()
505 except IOError:
505 except IOError:
506 self.warn("trouble committing %s!\n" % f)
506 self.warn("trouble committing %s!\n" % f)
507 raise
507 raise
508
508
509 r = self.file(f)
509 r = self.file(f)
510 fp1 = m1.get(f, nullid)
510 fp1 = m1.get(f, nullid)
511 fp2 = m2.get(f, nullid)
511 fp2 = m2.get(f, nullid)
512 new[f] = r.add(t, tr, linkrev, fp1, fp2)
512 new[f] = r.add(t, tr, linkrev, fp1, fp2)
513
513
514 # update manifest
514 # update manifest
515 m1.update(new)
515 m1.update(new)
516 for f in remove: del m1[f]
516 for f in remove: del m1[f]
517 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
517 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
518
518
519 # add changeset
519 # add changeset
520 new = new.keys()
520 new = new.keys()
521 new.sort()
521 new.sort()
522
522
523 if not text:
523 if not text:
524 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
524 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
525 edittext += "".join(["HG: changed %s\n" % f for f in new])
525 edittext += "".join(["HG: changed %s\n" % f for f in new])
526 edittext += "".join(["HG: removed %s\n" % f for f in remove])
526 edittext += "".join(["HG: removed %s\n" % f for f in remove])
527 edittext = self.ui.edit(edittext)
527 edittext = self.ui.edit(edittext)
528 if not edittext.rstrip():
528 if not edittext.rstrip():
529 return 1
529 return 1
530 text = edittext
530 text = edittext
531
531
532 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
532 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
533 tr.close()
533 tr.close()
534
534
535 self.dirstate.setparents(n)
535 self.dirstate.setparents(n)
536 self.dirstate.update(new, "n")
536 self.dirstate.update(new, "n")
537 self.dirstate.forget(remove)
537 self.dirstate.forget(remove)
538
538
539 def diffdir(self, path, changeset = None):
539 def diffdir(self, path, changeset = None):
540 changed = []
540 changed = []
541 added = []
541 added = []
542 unknown = []
542 unknown = []
543 mf = {}
543 mf = {}
544
544
545 if changeset:
545 if changeset:
546 change = self.changelog.read(changeset)
546 change = self.changelog.read(changeset)
547 mf = self.manifest.read(change[0])
547 mf = self.manifest.read(change[0])
548 dc = dict.fromkeys(mf)
548 dc = dict.fromkeys(mf)
549 else:
549 else:
550 changeset = self.dirstate.parents()[0]
550 changeset = self.dirstate.parents()[0]
551 change = self.changelog.read(changeset)
551 change = self.changelog.read(changeset)
552 mf = self.manifest.read(change[0])
552 mf = self.manifest.read(change[0])
553 dc = self.dirstate.copy()
553 dc = self.dirstate.copy()
554
554
555 def fcmp(fn):
555 def fcmp(fn):
556 t1 = self.wfile(fn).read()
556 t1 = self.wfile(fn).read()
557 t2 = self.file(fn).revision(mf[fn])
557 t2 = self.file(fn).revision(mf[fn])
558 return cmp(t1, t2)
558 return cmp(t1, t2)
559
559
560 for dir, subdirs, files in os.walk(path):
560 for dir, subdirs, files in os.walk(path):
561 d = dir[len(self.root)+1:]
561 d = dir[len(self.root)+1:]
562 if ".hg" in subdirs: subdirs.remove(".hg")
562 if ".hg" in subdirs: subdirs.remove(".hg")
563
563
564 for f in files:
564 for f in files:
565 fn = os.path.join(d, f)
565 fn = os.path.join(d, f)
566 try: s = os.stat(os.path.join(self.root, fn))
566 try: s = os.stat(os.path.join(self.root, fn))
567 except: continue
567 except: continue
568 if fn in dc:
568 if fn in dc:
569 c = dc[fn]
569 c = dc[fn]
570 del dc[fn]
570 del dc[fn]
571 if not c:
571 if not c:
572 if fcmp(fn):
572 if fcmp(fn):
573 changed.append(fn)
573 changed.append(fn)
574 elif c[0] == 'm':
574 elif c[0] == 'm':
575 changed.append(fn)
575 changed.append(fn)
576 elif c[0] == 'a':
576 elif c[0] == 'a':
577 added.append(fn)
577 added.append(fn)
578 elif c[0] == 'r':
578 elif c[0] == 'r':
579 unknown.append(fn)
579 unknown.append(fn)
580 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
580 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
581 changed.append(fn)
581 changed.append(fn)
582 elif c[1] != s.st_mode or c[3] != s.st_mtime:
582 elif c[1] != s.st_mode or c[3] != s.st_mtime:
583 if fcmp(fn):
583 if fcmp(fn):
584 changed.append(fn)
584 changed.append(fn)
585 else:
585 else:
586 if self.ignore(fn): continue
586 if self.ignore(fn): continue
587 unknown.append(fn)
587 unknown.append(fn)
588
588
589 deleted = dc.keys()
589 deleted = dc.keys()
590 deleted.sort()
590 deleted.sort()
591
591
592 return (changed, added, deleted, unknown)
592 return (changed, added, deleted, unknown)
593
593
594 def diffrevs(self, node1, node2):
594 def diffrevs(self, node1, node2):
595 changed, added = [], []
595 changed, added = [], []
596
596
597 change = self.changelog.read(node1)
597 change = self.changelog.read(node1)
598 mf1 = self.manifest.read(change[0])
598 mf1 = self.manifest.read(change[0])
599 change = self.changelog.read(node2)
599 change = self.changelog.read(node2)
600 mf2 = self.manifest.read(change[0])
600 mf2 = self.manifest.read(change[0])
601
601
602 for fn in mf2:
602 for fn in mf2:
603 if mf1.has_key(fn):
603 if mf1.has_key(fn):
604 if mf1[fn] != mf2[fn]:
604 if mf1[fn] != mf2[fn]:
605 changed.append(fn)
605 changed.append(fn)
606 del mf1[fn]
606 del mf1[fn]
607 else:
607 else:
608 added.append(fn)
608 added.append(fn)
609
609
610 deleted = mf1.keys()
610 deleted = mf1.keys()
611 deleted.sort()
611 deleted.sort()
612
612
613 return (changed, added, deleted)
613 return (changed, added, deleted)
614
614
615 def add(self, list):
615 def add(self, list):
616 for f in list:
616 for f in list:
617 p = self.wjoin(f)
617 p = self.wjoin(f)
618 if not os.path.isfile(p):
618 if not os.path.isfile(p):
619 self.ui.warn("%s does not exist!\n" % f)
619 self.ui.warn("%s does not exist!\n" % f)
620 elif self.dirstate.state(f) == 'n':
620 elif self.dirstate.state(f) == 'n':
621 self.ui.warn("%s already tracked!\n" % f)
621 self.ui.warn("%s already tracked!\n" % f)
622 else:
622 else:
623 self.dirstate.update([f], "a")
623 self.dirstate.update([f], "a")
624
624
625 def forget(self, list):
625 def forget(self, list):
626 for f in list:
626 for f in list:
627 if self.dirstate.state(f) not in 'ai':
627 if self.dirstate.state(f) not in 'ai':
628 self.ui.warn("%s not added!\n" % f)
628 self.ui.warn("%s not added!\n" % f)
629 else:
629 else:
630 self.dirstate.forget([f])
630 self.dirstate.forget([f])
631
631
632 def remove(self, list):
632 def remove(self, list):
633 for f in list:
633 for f in list:
634 p = self.wjoin(f)
634 p = self.wjoin(f)
635 if os.path.isfile(p):
635 if os.path.isfile(p):
636 self.ui.warn("%s still exists!\n" % f)
636 self.ui.warn("%s still exists!\n" % f)
637 elif f not in self.dirstate:
637 elif f not in self.dirstate:
638 self.ui.warn("%s not tracked!\n" % f)
638 self.ui.warn("%s not tracked!\n" % f)
639 else:
639 else:
640 self.dirstate.update([f], "r")
640 self.dirstate.update([f], "r")
641
641
642 def heads(self):
642 def heads(self):
643 return self.changelog.heads()
643 return self.changelog.heads()
644
644
645 def branches(self, nodes):
645 def branches(self, nodes):
646 if not nodes: nodes = [self.changelog.tip()]
646 if not nodes: nodes = [self.changelog.tip()]
647 b = []
647 b = []
648 for n in nodes:
648 for n in nodes:
649 t = n
649 t = n
650 while n:
650 while n:
651 p = self.changelog.parents(n)
651 p = self.changelog.parents(n)
652 if p[1] != nullid or p[0] == nullid:
652 if p[1] != nullid or p[0] == nullid:
653 b.append((t, n, p[0], p[1]))
653 b.append((t, n, p[0], p[1]))
654 break
654 break
655 n = p[0]
655 n = p[0]
656 return b
656 return b
657
657
658 def between(self, pairs):
658 def between(self, pairs):
659 r = []
659 r = []
660
660
661 for top, bottom in pairs:
661 for top, bottom in pairs:
662 n, l, i = top, [], 0
662 n, l, i = top, [], 0
663 f = 1
663 f = 1
664
664
665 while n != bottom:
665 while n != bottom:
666 p = self.changelog.parents(n)[0]
666 p = self.changelog.parents(n)[0]
667 if i == f:
667 if i == f:
668 l.append(n)
668 l.append(n)
669 f = f * 2
669 f = f * 2
670 n = p
670 n = p
671 i += 1
671 i += 1
672
672
673 r.append(l)
673 r.append(l)
674
674
675 return r
675 return r
676
676
677 def newer(self, nodes):
677 def newer(self, nodes):
678 m = {}
678 m = {}
679 nl = []
679 nl = []
680 pm = {}
680 pm = {}
681 cl = self.changelog
681 cl = self.changelog
682 t = l = cl.count()
682 t = l = cl.count()
683
683
684 # find the lowest numbered node
684 # find the lowest numbered node
685 for n in nodes:
685 for n in nodes:
686 l = min(l, cl.rev(n))
686 l = min(l, cl.rev(n))
687 m[n] = 1
687 m[n] = 1
688
688
689 for i in xrange(l, t):
689 for i in xrange(l, t):
690 n = cl.node(i)
690 n = cl.node(i)
691 if n in m: # explicitly listed
691 if n in m: # explicitly listed
692 pm[n] = 1
692 pm[n] = 1
693 nl.append(n)
693 nl.append(n)
694 continue
694 continue
695 for p in cl.parents(n):
695 for p in cl.parents(n):
696 if p in pm: # parent listed
696 if p in pm: # parent listed
697 pm[n] = 1
697 pm[n] = 1
698 nl.append(n)
698 nl.append(n)
699 break
699 break
700
700
701 return nl
701 return nl
702
702
703 def getchangegroup(self, remote):
703 def getchangegroup(self, remote):
704 m = self.changelog.nodemap
704 m = self.changelog.nodemap
705 search = []
705 search = []
706 fetch = []
706 fetch = []
707 seen = {}
707 seen = {}
708 seenbranch = {}
708 seenbranch = {}
709
709
710 # if we have an empty repo, fetch everything
710 # if we have an empty repo, fetch everything
711 if self.changelog.tip() == nullid:
711 if self.changelog.tip() == nullid:
712 self.ui.status("requesting all changes\n")
712 self.ui.status("requesting all changes\n")
713 return remote.changegroup([nullid])
713 return remote.changegroup([nullid])
714
714
715 # otherwise, assume we're closer to the tip than the root
715 # otherwise, assume we're closer to the tip than the root
716 self.ui.status("searching for changes\n")
716 self.ui.status("searching for changes\n")
717 heads = remote.heads()
717 heads = remote.heads()
718 unknown = []
718 unknown = []
719 for h in heads:
719 for h in heads:
720 if h not in m:
720 if h not in m:
721 unknown.append(h)
721 unknown.append(h)
722
722
723 if not unknown:
723 if not unknown:
724 self.ui.status("nothing to do!\n")
724 self.ui.status("nothing to do!\n")
725 return None
725 return None
726
726
727 unknown = remote.branches(unknown)
727 unknown = remote.branches(unknown)
728 while unknown:
728 while unknown:
729 n = unknown.pop(0)
729 n = unknown.pop(0)
730 seen[n[0]] = 1
730 seen[n[0]] = 1
731
731
732 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
732 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
733 if n == nullid: break
733 if n == nullid: break
734 if n in seenbranch:
734 if n in seenbranch:
735 self.ui.debug("branch already found\n")
735 self.ui.debug("branch already found\n")
736 continue
736 continue
737 if n[1] and n[1] in m: # do we know the base?
737 if n[1] and n[1] in m: # do we know the base?
738 self.ui.debug("found incomplete branch %s:%s\n"
738 self.ui.debug("found incomplete branch %s:%s\n"
739 % (short(n[0]), short(n[1])))
739 % (short(n[0]), short(n[1])))
740 search.append(n) # schedule branch range for scanning
740 search.append(n) # schedule branch range for scanning
741 seenbranch[n] = 1
741 seenbranch[n] = 1
742 else:
742 else:
743 if n[2] in m and n[3] in m:
743 if n[2] in m and n[3] in m:
744 if n[1] not in fetch:
744 if n[1] not in fetch:
745 self.ui.debug("found new changeset %s\n" %
745 self.ui.debug("found new changeset %s\n" %
746 short(n[1]))
746 short(n[1]))
747 fetch.append(n[1]) # earliest unknown
747 fetch.append(n[1]) # earliest unknown
748 continue
748 continue
749
749
750 r = []
750 r = []
751 for a in n[2:4]:
751 for a in n[2:4]:
752 if a not in seen: r.append(a)
752 if a not in seen: r.append(a)
753
753
754 if r:
754 if r:
755 self.ui.debug("requesting %s\n" %
755 self.ui.debug("requesting %s\n" %
756 " ".join(map(short, r)))
756 " ".join(map(short, r)))
757 for b in remote.branches(r):
757 for b in remote.branches(r):
758 self.ui.debug("received %s:%s\n" %
758 self.ui.debug("received %s:%s\n" %
759 (short(b[0]), short(b[1])))
759 (short(b[0]), short(b[1])))
760 if b[0] not in m and b[0] not in seen:
760 if b[0] not in m and b[0] not in seen:
761 unknown.append(b)
761 unknown.append(b)
762
762
763 while search:
763 while search:
764 n = search.pop(0)
764 n = search.pop(0)
765 l = remote.between([(n[0], n[1])])[0]
765 l = remote.between([(n[0], n[1])])[0]
766 p = n[0]
766 p = n[0]
767 f = 1
767 f = 1
768 for i in l + [n[1]]:
768 for i in l + [n[1]]:
769 if i in m:
769 if i in m:
770 if f <= 2:
770 if f <= 2:
771 self.ui.debug("found new branch changeset %s\n" %
771 self.ui.debug("found new branch changeset %s\n" %
772 short(p))
772 short(p))
773 fetch.append(p)
773 fetch.append(p)
774 else:
774 else:
775 self.ui.debug("narrowed branch search to %s:%s\n"
775 self.ui.debug("narrowed branch search to %s:%s\n"
776 % (short(p), short(i)))
776 % (short(p), short(i)))
777 search.append((p, i))
777 search.append((p, i))
778 break
778 break
779 p, f = i, f * 2
779 p, f = i, f * 2
780
780
781 for f in fetch:
781 for f in fetch:
782 if f in m:
782 if f in m:
783 raise "already have", short(f[:4])
783 raise "already have", short(f[:4])
784
784
785 self.ui.note("adding new changesets starting at " +
785 self.ui.note("adding new changesets starting at " +
786 " ".join([short(f) for f in fetch]) + "\n")
786 " ".join([short(f) for f in fetch]) + "\n")
787
787
788 return remote.changegroup(fetch)
788 return remote.changegroup(fetch)
789
789
790 def changegroup(self, basenodes):
790 def changegroup(self, basenodes):
791 nodes = self.newer(basenodes)
791 nodes = self.newer(basenodes)
792
792
793 # construct the link map
793 # construct the link map
794 linkmap = {}
794 linkmap = {}
795 for n in nodes:
795 for n in nodes:
796 linkmap[self.changelog.rev(n)] = n
796 linkmap[self.changelog.rev(n)] = n
797
797
798 # construct a list of all changed files
798 # construct a list of all changed files
799 changed = {}
799 changed = {}
800 for n in nodes:
800 for n in nodes:
801 c = self.changelog.read(n)
801 c = self.changelog.read(n)
802 for f in c[3]:
802 for f in c[3]:
803 changed[f] = 1
803 changed[f] = 1
804 changed = changed.keys()
804 changed = changed.keys()
805 changed.sort()
805 changed.sort()
806
806
807 # the changegroup is changesets + manifests + all file revs
807 # the changegroup is changesets + manifests + all file revs
808 revs = [ self.changelog.rev(n) for n in nodes ]
808 revs = [ self.changelog.rev(n) for n in nodes ]
809
809
810 for y in self.changelog.group(linkmap): yield y
810 for y in self.changelog.group(linkmap): yield y
811 for y in self.manifest.group(linkmap): yield y
811 for y in self.manifest.group(linkmap): yield y
812 for f in changed:
812 for f in changed:
813 yield struct.pack(">l", len(f) + 4) + f
813 yield struct.pack(">l", len(f) + 4) + f
814 g = self.file(f).group(linkmap)
814 g = self.file(f).group(linkmap)
815 for y in g:
815 for y in g:
816 yield y
816 yield y
817
817
818 def addchangegroup(self, generator):
818 def addchangegroup(self, generator):
819
819
820 class genread:
820 class genread:
821 def __init__(self, generator):
821 def __init__(self, generator):
822 self.g = generator
822 self.g = generator
823 self.buf = ""
823 self.buf = ""
824 def read(self, l):
824 def read(self, l):
825 while l > len(self.buf):
825 while l > len(self.buf):
826 try:
826 try:
827 self.buf += self.g.next()
827 self.buf += self.g.next()
828 except StopIteration:
828 except StopIteration:
829 break
829 break
830 d, self.buf = self.buf[:l], self.buf[l:]
830 d, self.buf = self.buf[:l], self.buf[l:]
831 return d
831 return d
832
832
833 def getchunk():
833 def getchunk():
834 d = source.read(4)
834 d = source.read(4)
835 if not d: return ""
835 if not d: return ""
836 l = struct.unpack(">l", d)[0]
836 l = struct.unpack(">l", d)[0]
837 if l <= 4: return ""
837 if l <= 4: return ""
838 return source.read(l - 4)
838 return source.read(l - 4)
839
839
840 def getgroup():
840 def getgroup():
841 while 1:
841 while 1:
842 c = getchunk()
842 c = getchunk()
843 if not c: break
843 if not c: break
844 yield c
844 yield c
845
845
846 def csmap(x):
846 def csmap(x):
847 self.ui.debug("add changeset %s\n" % short(x))
847 self.ui.debug("add changeset %s\n" % short(x))
848 return self.changelog.count()
848 return self.changelog.count()
849
849
850 def revmap(x):
850 def revmap(x):
851 return self.changelog.rev(x)
851 return self.changelog.rev(x)
852
852
853 if not generator: return
853 if not generator: return
854 changesets = files = revisions = 0
854 changesets = files = revisions = 0
855
855
856 source = genread(generator)
856 source = genread(generator)
857 lock = self.lock()
857 lock = self.lock()
858 tr = self.transaction()
858 tr = self.transaction()
859
859
860 # pull off the changeset group
860 # pull off the changeset group
861 self.ui.status("adding changesets\n")
861 self.ui.status("adding changesets\n")
862 co = self.changelog.tip()
862 co = self.changelog.tip()
863 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
863 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
864 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
864 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
865
865
866 # pull off the manifest group
866 # pull off the manifest group
867 self.ui.status("adding manifests\n")
867 self.ui.status("adding manifests\n")
868 mm = self.manifest.tip()
868 mm = self.manifest.tip()
869 mo = self.manifest.addgroup(getgroup(), revmap, tr)
869 mo = self.manifest.addgroup(getgroup(), revmap, tr)
870
870
871 # process the files
871 # process the files
872 self.ui.status("adding file revisions\n")
872 self.ui.status("adding file revisions\n")
873 while 1:
873 while 1:
874 f = getchunk()
874 f = getchunk()
875 if not f: break
875 if not f: break
876 self.ui.debug("adding %s revisions\n" % f)
876 self.ui.debug("adding %s revisions\n" % f)
877 fl = self.file(f)
877 fl = self.file(f)
878 o = fl.tip()
878 o = fl.tip()
879 n = fl.addgroup(getgroup(), revmap, tr)
879 n = fl.addgroup(getgroup(), revmap, tr)
880 revisions += fl.rev(n) - fl.rev(o)
880 revisions += fl.rev(n) - fl.rev(o)
881 files += 1
881 files += 1
882
882
883 self.ui.status(("modified %d files, added %d changesets" +
883 self.ui.status(("modified %d files, added %d changesets" +
884 " and %d new revisions\n")
884 " and %d new revisions\n")
885 % (files, changesets, revisions))
885 % (files, changesets, revisions))
886
886
887 tr.close()
887 tr.close()
888 return
888 return
889
889
890 def update(self, node, allow=False, force=False):
890 def update(self, node, allow=False, force=False):
891 pl = self.dirstate.parents()
891 pl = self.dirstate.parents()
892 if not force and pl[1] != nullid:
892 if not force and pl[1] != nullid:
893 self.ui.warn("aborting: outstanding uncommitted merges\n")
893 self.ui.warn("aborting: outstanding uncommitted merges\n")
894 return
894 return
895
895
896 p1, p2 = pl[0], node
896 p1, p2 = pl[0], node
897 pa = self.changelog.ancestor(p1, p2)
897 pa = self.changelog.ancestor(p1, p2)
898 m1n = self.changelog.read(p1)[0]
898 m1n = self.changelog.read(p1)[0]
899 m2n = self.changelog.read(p2)[0]
899 m2n = self.changelog.read(p2)[0]
900 man = self.manifest.ancestor(m1n, m2n)
900 man = self.manifest.ancestor(m1n, m2n)
901 m1 = self.manifest.read(m1n)
901 m1 = self.manifest.read(m1n)
902 mf1 = self.manifest.readflags(m1n)
902 mf1 = self.manifest.readflags(m1n)
903 m2 = self.manifest.read(m2n)
903 m2 = self.manifest.read(m2n)
904 mf2 = self.manifest.readflags(m2n)
904 mf2 = self.manifest.readflags(m2n)
905 ma = self.manifest.read(man)
905 ma = self.manifest.read(man)
906 mfa = self.manifest.readflags(m2n)
906 mfa = self.manifest.readflags(m2n)
907
907
908 (c, a, d, u) = self.diffdir(self.root)
908 (c, a, d, u) = self.diffdir(self.root)
909
909
910 # resolve the manifest to determine which files
910 # resolve the manifest to determine which files
911 # we care about merging
911 # we care about merging
912 self.ui.note("resolving manifests\n")
912 self.ui.note("resolving manifests\n")
913 self.ui.debug(" ancestor %s local %s remote %s\n" %
913 self.ui.debug(" ancestor %s local %s remote %s\n" %
914 (short(man), short(m1n), short(m2n)))
914 (short(man), short(m1n), short(m2n)))
915
915
916 merge = {}
916 merge = {}
917 get = {}
917 get = {}
918 remove = []
918 remove = []
919 mark = {}
919 mark = {}
920
920
921 # construct a working dir manifest
921 # construct a working dir manifest
922 mw = m1.copy()
922 mw = m1.copy()
923 mfw = mf1.copy()
923 mfw = mf1.copy()
924 for f in a + c + u:
924 for f in a + c + u:
925 mw[f] = ""
925 mw[f] = ""
926 mfw[f] = is_exec(self.wjoin(f))
926 mfw[f] = is_exec(self.wjoin(f))
927 for f in d:
927 for f in d:
928 if f in mw: del mw[f]
928 if f in mw: del mw[f]
929
929
930 for f, n in mw.iteritems():
930 for f, n in mw.iteritems():
931 if f in m2:
931 if f in m2:
932 s = 0
932 s = 0
933
933
934 # are files different?
934 # are files different?
935 if n != m2[f]:
935 if n != m2[f]:
936 a = ma.get(f, nullid)
936 a = ma.get(f, nullid)
937 # are both different from the ancestor?
937 # are both different from the ancestor?
938 if n != a and m2[f] != a:
938 if n != a and m2[f] != a:
939 self.ui.debug(" %s versions differ, resolve\n" % f)
939 self.ui.debug(" %s versions differ, resolve\n" % f)
940 merge[f] = (m1.get(f, nullid), m2[f])
940 merge[f] = (m1.get(f, nullid), m2[f])
941 # merge executable bits
941 # merge executable bits
942 # "if we changed or they changed, change in merge"
942 # "if we changed or they changed, change in merge"
943 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
943 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
944 mode = ((a^b) | (a^c)) ^ a
944 mode = ((a^b) | (a^c)) ^ a
945 merge[f] = (m1.get(f, nullid), m2[f], mode)
945 merge[f] = (m1.get(f, nullid), m2[f], mode)
946 s = 1
946 s = 1
947 # are we clobbering?
947 # are we clobbering?
948 # is remote's version newer?
948 # is remote's version newer?
949 # or are we going back in time?
949 # or are we going back in time?
950 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
950 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
951 self.ui.debug(" remote %s is newer, get\n" % f)
951 self.ui.debug(" remote %s is newer, get\n" % f)
952 get[f] = m2[f]
952 get[f] = m2[f]
953 s = 1
953 s = 1
954 else:
954 else:
955 mark[f] = 1
955 mark[f] = 1
956
956
957 if not s and mfw[f] != mf2[f]:
957 if not s and mfw[f] != mf2[f]:
958 if force:
958 if force:
959 self.ui.debug(" updating permissions for %s\n" % f)
959 self.ui.debug(" updating permissions for %s\n" % f)
960 set_exec(self.wjoin(f), mf2[f])
960 set_exec(self.wjoin(f), mf2[f])
961 else:
961 else:
962 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
962 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
963 mode = ((a^b) | (a^c)) ^ a
963 mode = ((a^b) | (a^c)) ^ a
964 if mode != b:
964 if mode != b:
965 self.ui.debug(" updating permissions for %s\n" % f)
965 self.ui.debug(" updating permissions for %s\n" % f)
966 set_exec(self.wjoin(f), mode)
966 set_exec(self.wjoin(f), mode)
967 mark[f] = 1
967 mark[f] = 1
968 del m2[f]
968 del m2[f]
969 elif f in ma:
969 elif f in ma:
970 if not force and n != ma[f]:
970 if not force and n != ma[f]:
971 r = self.ui.prompt(
971 r = self.ui.prompt(
972 (" local changed %s which remote deleted\n" % f) +
972 (" local changed %s which remote deleted\n" % f) +
973 "(k)eep or (d)elete?", "[kd]", "k")
973 "(k)eep or (d)elete?", "[kd]", "k")
974 if r == "d":
974 if r == "d":
975 remove.append(f)
975 remove.append(f)
976 else:
976 else:
977 self.ui.debug("other deleted %s\n" % f)
977 self.ui.debug("other deleted %s\n" % f)
978 remove.append(f) # other deleted it
978 remove.append(f) # other deleted it
979 else:
979 else:
980 if n == m1.get(f, nullid): # same as parent
980 if n == m1.get(f, nullid): # same as parent
981 self.ui.debug("remote deleted %s\n" % f)
981 self.ui.debug("remote deleted %s\n" % f)
982 remove.append(f)
982 remove.append(f)
983 else:
983 else:
984 self.ui.debug("working dir created %s, keeping\n" % f)
984 self.ui.debug("working dir created %s, keeping\n" % f)
985
985
986 for f, n in m2.iteritems():
986 for f, n in m2.iteritems():
987 if f[0] == "/": continue
987 if f[0] == "/": continue
988 if not force and f in ma and n != ma[f]:
988 if not force and f in ma and n != ma[f]:
989 r = self.ui.prompt(
989 r = self.ui.prompt(
990 ("remote changed %s which local deleted\n" % f) +
990 ("remote changed %s which local deleted\n" % f) +
991 "(k)eep or (d)elete?", "[kd]", "k")
991 "(k)eep or (d)elete?", "[kd]", "k")
992 if r == "d": remove.append(f)
992 if r == "d": remove.append(f)
993 else:
993 else:
994 self.ui.debug("remote created %s\n" % f)
994 self.ui.debug("remote created %s\n" % f)
995 get[f] = n
995 get[f] = n
996
996
997 del mw, m1, m2, ma
997 del mw, m1, m2, ma
998
998
999 if force:
999 if force:
1000 for f in merge:
1000 for f in merge:
1001 get[f] = merge[f][1]
1001 get[f] = merge[f][1]
1002 merge = {}
1002 merge = {}
1003
1003
1004 if pa == p1 or pa == p2:
1004 if pa == p1 or pa == p2:
1005 # we don't need to do any magic, just jump to the new rev
1005 # we don't need to do any magic, just jump to the new rev
1006 mode = 'n'
1006 mode = 'n'
1007 p1, p2 = p2, nullid
1007 p1, p2 = p2, nullid
1008 else:
1008 else:
1009 if not allow:
1009 if not allow:
1010 self.ui.status("this update spans a branch" +
1010 self.ui.status("this update spans a branch" +
1011 " affecting the following files:\n")
1011 " affecting the following files:\n")
1012 fl = merge.keys() + get.keys()
1012 fl = merge.keys() + get.keys()
1013 fl.sort()
1013 fl.sort()
1014 for f in fl:
1014 for f in fl:
1015 cf = ""
1015 cf = ""
1016 if f in merge: cf = " (resolve)"
1016 if f in merge: cf = " (resolve)"
1017 self.ui.status(" %s%s\n" % (f, cf))
1017 self.ui.status(" %s%s\n" % (f, cf))
1018 self.ui.warn("aborting update spanning branches!\n")
1018 self.ui.warn("aborting update spanning branches!\n")
1019 self.ui.status("(use update -m to perform a branch merge)\n")
1019 self.ui.status("(use update -m to perform a branch merge)\n")
1020 return 1
1020 return 1
1021 # we have to remember what files we needed to get/change
1021 # we have to remember what files we needed to get/change
1022 # because any file that's different from either one of its
1022 # because any file that's different from either one of its
1023 # parents must be in the changeset
1023 # parents must be in the changeset
1024 mode = 'm'
1024 mode = 'm'
1025 self.dirstate.update(mark.keys(), "m")
1025 self.dirstate.update(mark.keys(), "m")
1026
1026
1027 self.dirstate.setparents(p1, p2)
1027 self.dirstate.setparents(p1, p2)
1028
1028
1029 # get the files we don't need to change
1029 # get the files we don't need to change
1030 files = get.keys()
1030 files = get.keys()
1031 files.sort()
1031 files.sort()
1032 for f in files:
1032 for f in files:
1033 if f[0] == "/": continue
1033 if f[0] == "/": continue
1034 self.ui.note("getting %s\n" % f)
1034 self.ui.note("getting %s\n" % f)
1035 t = self.file(f).read(get[f])
1035 t = self.file(f).read(get[f])
1036 try:
1036 try:
1037 self.wfile(f, "w").write(t)
1037 self.wfile(f, "w").write(t)
1038 except IOError:
1038 except IOError:
1039 os.makedirs(os.path.dirname(self.wjoin(f)))
1039 os.makedirs(os.path.dirname(self.wjoin(f)))
1040 self.wfile(f, "w").write(t)
1040 self.wfile(f, "w").write(t)
1041 set_exec(self.wjoin(f), mf2[f])
1041 set_exec(self.wjoin(f), mf2[f])
1042 self.dirstate.update([f], mode)
1042 self.dirstate.update([f], mode)
1043
1043
1044 # merge the tricky bits
1044 # merge the tricky bits
1045 files = merge.keys()
1045 files = merge.keys()
1046 files.sort()
1046 files.sort()
1047 for f in files:
1047 for f in files:
1048 self.ui.status("merging %s\n" % f)
1048 self.ui.status("merging %s\n" % f)
1049 m, o, flag = merge[f]
1049 m, o, flag = merge[f]
1050 self.merge3(f, m, o)
1050 self.merge3(f, m, o)
1051 set_exec(self.wjoin(f), flag)
1051 set_exec(self.wjoin(f), flag)
1052 self.dirstate.update([f], 'm')
1052 self.dirstate.update([f], 'm')
1053
1053
1054 for f in remove:
1054 for f in remove:
1055 self.ui.note("removing %s\n" % f)
1055 self.ui.note("removing %s\n" % f)
1056 os.unlink(f)
1056 os.unlink(f)
1057 if mode == 'n':
1057 if mode == 'n':
1058 self.dirstate.forget(remove)
1058 self.dirstate.forget(remove)
1059 else:
1059 else:
1060 self.dirstate.update(remove, 'r')
1060 self.dirstate.update(remove, 'r')
1061
1061
1062 def merge3(self, fn, my, other):
1062 def merge3(self, fn, my, other):
1063 """perform a 3-way merge in the working directory"""
1063 """perform a 3-way merge in the working directory"""
1064
1064
1065 def temp(prefix, node):
1065 def temp(prefix, node):
1066 pre = "%s~%s." % (os.path.basename(fn), prefix)
1066 pre = "%s~%s." % (os.path.basename(fn), prefix)
1067 (fd, name) = tempfile.mkstemp("", pre)
1067 (fd, name) = tempfile.mkstemp("", pre)
1068 f = os.fdopen(fd, "w")
1068 f = os.fdopen(fd, "w")
1069 f.write(fl.revision(node))
1069 f.write(fl.revision(node))
1070 f.close()
1070 f.close()
1071 return name
1071 return name
1072
1072
1073 fl = self.file(fn)
1073 fl = self.file(fn)
1074 base = fl.ancestor(my, other)
1074 base = fl.ancestor(my, other)
1075 a = self.wjoin(fn)
1075 a = self.wjoin(fn)
1076 b = temp("other", other)
1076 b = temp("other", other)
1077 c = temp("base", base)
1077 c = temp("base", base)
1078
1078
1079 self.ui.note("resolving %s\n" % fn)
1079 self.ui.note("resolving %s\n" % fn)
1080 self.ui.debug("file %s: other %s ancestor %s\n" %
1080 self.ui.debug("file %s: other %s ancestor %s\n" %
1081 (fn, short(other), short(base)))
1081 (fn, short(other), short(base)))
1082
1082
1083 cmd = os.environ.get("HGMERGE", "hgmerge")
1083 cmd = os.environ.get("HGMERGE", "hgmerge")
1084 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1084 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1085 if r:
1085 if r:
1086 self.ui.warn("merging %s failed!\n" % fn)
1086 self.ui.warn("merging %s failed!\n" % fn)
1087
1087
1088 os.unlink(b)
1088 os.unlink(b)
1089 os.unlink(c)
1089 os.unlink(c)
1090
1090
1091 def verify(self):
1091 def verify(self):
1092 filelinkrevs = {}
1092 filelinkrevs = {}
1093 filenodes = {}
1093 filenodes = {}
1094 changesets = revisions = files = 0
1094 changesets = revisions = files = 0
1095 errors = 0
1095 errors = 0
1096
1096
1097 seen = {}
1097 seen = {}
1098 self.ui.status("checking changesets\n")
1098 self.ui.status("checking changesets\n")
1099 for i in range(self.changelog.count()):
1099 for i in range(self.changelog.count()):
1100 changesets += 1
1100 changesets += 1
1101 n = self.changelog.node(i)
1101 n = self.changelog.node(i)
1102 if n in seen:
1102 if n in seen:
1103 self.ui.warn("duplicate changeset at revision %d\n" % i)
1103 self.ui.warn("duplicate changeset at revision %d\n" % i)
1104 errors += 1
1104 errors += 1
1105 seen[n] = 1
1105 seen[n] = 1
1106
1106
1107 for p in self.changelog.parents(n):
1107 for p in self.changelog.parents(n):
1108 if p not in self.changelog.nodemap:
1108 if p not in self.changelog.nodemap:
1109 self.ui.warn("changeset %s has unknown parent %s\n" %
1109 self.ui.warn("changeset %s has unknown parent %s\n" %
1110 (short(n), short(p)))
1110 (short(n), short(p)))
1111 errors += 1
1111 errors += 1
1112 try:
1112 try:
1113 changes = self.changelog.read(n)
1113 changes = self.changelog.read(n)
1114 except Exception, inst:
1114 except Exception, inst:
1115 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1115 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1116 errors += 1
1116 errors += 1
1117
1117
1118 for f in changes[3]:
1118 for f in changes[3]:
1119 filelinkrevs.setdefault(f, []).append(i)
1119 filelinkrevs.setdefault(f, []).append(i)
1120
1120
1121 seen = {}
1121 seen = {}
1122 self.ui.status("checking manifests\n")
1122 self.ui.status("checking manifests\n")
1123 for i in range(self.manifest.count()):
1123 for i in range(self.manifest.count()):
1124 n = self.manifest.node(i)
1124 n = self.manifest.node(i)
1125 if n in seen:
1125 if n in seen:
1126 self.ui.warn("duplicate manifest at revision %d\n" % i)
1126 self.ui.warn("duplicate manifest at revision %d\n" % i)
1127 errors += 1
1127 errors += 1
1128 seen[n] = 1
1128 seen[n] = 1
1129
1129
1130 for p in self.manifest.parents(n):
1130 for p in self.manifest.parents(n):
1131 if p not in self.manifest.nodemap:
1131 if p not in self.manifest.nodemap:
1132 self.ui.warn("manifest %s has unknown parent %s\n" %
1132 self.ui.warn("manifest %s has unknown parent %s\n" %
1133 (short(n), short(p)))
1133 (short(n), short(p)))
1134 errors += 1
1134 errors += 1
1135
1135
1136 try:
1136 try:
1137 delta = mdiff.patchtext(self.manifest.delta(n))
1137 delta = mdiff.patchtext(self.manifest.delta(n))
1138 except KeyboardInterrupt:
1138 except KeyboardInterrupt:
1139 print "aborted"
1139 print "aborted"
1140 sys.exit(0)
1140 sys.exit(0)
1141 except Exception, inst:
1141 except Exception, inst:
1142 self.ui.warn("unpacking manifest %s: %s\n"
1142 self.ui.warn("unpacking manifest %s: %s\n"
1143 % (short(n), inst))
1143 % (short(n), inst))
1144 errors += 1
1144 errors += 1
1145
1145
1146 ff = [ l.split('\0') for l in delta.splitlines() ]
1146 ff = [ l.split('\0') for l in delta.splitlines() ]
1147 for f, fn in ff:
1147 for f, fn in ff:
1148 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1148 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1149
1149
1150 self.ui.status("crosschecking files in changesets and manifests\n")
1150 self.ui.status("crosschecking files in changesets and manifests\n")
1151 for f in filenodes:
1151 for f in filenodes:
1152 if f not in filelinkrevs:
1152 if f not in filelinkrevs:
1153 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1153 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1154 errors += 1
1154 errors += 1
1155
1155
1156 for f in filelinkrevs:
1156 for f in filelinkrevs:
1157 if f not in filenodes:
1157 if f not in filenodes:
1158 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1158 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1159 errors += 1
1159 errors += 1
1160
1160
1161 self.ui.status("checking files\n")
1161 self.ui.status("checking files\n")
1162 ff = filenodes.keys()
1162 ff = filenodes.keys()
1163 ff.sort()
1163 ff.sort()
1164 for f in ff:
1164 for f in ff:
1165 if f == "/dev/null": continue
1165 if f == "/dev/null": continue
1166 files += 1
1166 files += 1
1167 fl = self.file(f)
1167 fl = self.file(f)
1168 nodes = { nullid: 1 }
1168 nodes = { nullid: 1 }
1169 seen = {}
1169 seen = {}
1170 for i in range(fl.count()):
1170 for i in range(fl.count()):
1171 revisions += 1
1171 revisions += 1
1172 n = fl.node(i)
1172 n = fl.node(i)
1173
1173
1174 if n in seen:
1174 if n in seen:
1175 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1175 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1176 errors += 1
1176 errors += 1
1177
1177
1178 if n not in filenodes[f]:
1178 if n not in filenodes[f]:
1179 self.ui.warn("%s: %d:%s not in manifests\n"
1179 self.ui.warn("%s: %d:%s not in manifests\n"
1180 % (f, i, short(n)))
1180 % (f, i, short(n)))
1181 print len(filenodes[f].keys()), fl.count(), f
1181 print len(filenodes[f].keys()), fl.count(), f
1182 errors += 1
1182 errors += 1
1183 else:
1183 else:
1184 del filenodes[f][n]
1184 del filenodes[f][n]
1185
1185
1186 flr = fl.linkrev(n)
1186 flr = fl.linkrev(n)
1187 if flr not in filelinkrevs[f]:
1187 if flr not in filelinkrevs[f]:
1188 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1188 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1189 % (f, short(n), fl.linkrev(n)))
1189 % (f, short(n), fl.linkrev(n)))
1190 errors += 1
1190 errors += 1
1191 else:
1191 else:
1192 filelinkrevs[f].remove(flr)
1192 filelinkrevs[f].remove(flr)
1193
1193
1194 # verify contents
1194 # verify contents
1195 try:
1195 try:
1196 t = fl.read(n)
1196 t = fl.read(n)
1197 except Exception, inst:
1197 except Exception, inst:
1198 self.ui.warn("unpacking file %s %s: %s\n"
1198 self.ui.warn("unpacking file %s %s: %s\n"
1199 % (f, short(n), inst))
1199 % (f, short(n), inst))
1200 errors += 1
1200 errors += 1
1201
1201
1202 # verify parents
1202 # verify parents
1203 (p1, p2) = fl.parents(n)
1203 (p1, p2) = fl.parents(n)
1204 if p1 not in nodes:
1204 if p1 not in nodes:
1205 self.ui.warn("file %s:%s unknown parent 1 %s" %
1205 self.ui.warn("file %s:%s unknown parent 1 %s" %
1206 (f, short(n), short(p1)))
1206 (f, short(n), short(p1)))
1207 errors += 1
1207 errors += 1
1208 if p2 not in nodes:
1208 if p2 not in nodes:
1209 self.ui.warn("file %s:%s unknown parent 2 %s" %
1209 self.ui.warn("file %s:%s unknown parent 2 %s" %
1210 (f, short(n), short(p1)))
1210 (f, short(n), short(p1)))
1211 errors += 1
1211 errors += 1
1212 nodes[n] = 1
1212 nodes[n] = 1
1213
1213
1214 # cross-check
1214 # cross-check
1215 for node in filenodes[f]:
1215 for node in filenodes[f]:
1216 self.ui.warn("node %s in manifests not in %s\n"
1216 self.ui.warn("node %s in manifests not in %s\n"
1217 % (hex(n), f))
1217 % (hex(n), f))
1218 errors += 1
1218 errors += 1
1219
1219
1220 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1220 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1221 (files, changesets, revisions))
1221 (files, changesets, revisions))
1222
1222
1223 if errors:
1223 if errors:
1224 self.ui.warn("%d integrity errors encountered!\n" % errors)
1224 self.ui.warn("%d integrity errors encountered!\n" % errors)
1225 return 1
1225 return 1
1226
1226
1227 class remoterepository:
1227 class remoterepository:
1228 def __init__(self, ui, path):
1228 def __init__(self, ui, path):
1229 self.url = path
1229 self.url = path
1230 self.ui = ui
1230 self.ui = ui
1231 no_list = [ "localhost", "127.0.0.1" ]
1232 host = ui.config("http_proxy", "host")
1233 user = ui.config("http_proxy", "user")
1234 passwd = ui.config("http_proxy", "passwd")
1235 no = ui.config("http_proxy", "no")
1236 if no:
1237 no_list = no_list + no.split(",")
1238
1239 no_proxy = 0
1240 for h in no_list:
1241 if (path.startswith("http://" + h + "/") or
1242 path.startswith("http://" + h + ":") or
1243 path == "http://" + h):
1244 no_proxy = 1
1245
1246 # Note: urllib2 takes proxy values from the environment and those will
1247 # take precedence
1248
1249 proxy_handler = urllib2.BaseHandler()
1250 if host and not no_proxy:
1251 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1252
1253 authinfo = None
1254 if user and passwd:
1255 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1256 passmgr.add_password(None, host, user, passwd)
1257 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1258
1259 opener = urllib2.build_opener(proxy_handler, authinfo)
1260 urllib2.install_opener(opener)
1231
1261
1232 def do_cmd(self, cmd, **args):
1262 def do_cmd(self, cmd, **args):
1233 self.ui.debug("sending %s command\n" % cmd)
1263 self.ui.debug("sending %s command\n" % cmd)
1234 q = {"cmd": cmd}
1264 q = {"cmd": cmd}
1235 q.update(args)
1265 q.update(args)
1236 qs = urllib.urlencode(q)
1266 qs = urllib.urlencode(q)
1237 cu = "%s?%s" % (self.url, qs)
1267 cu = "%s?%s" % (self.url, qs)
1238 return urllib.urlopen(cu)
1268 return urllib2.urlopen(cu)
1239
1269
1240 def heads(self):
1270 def heads(self):
1241 d = self.do_cmd("heads").read()
1271 d = self.do_cmd("heads").read()
1242 try:
1272 try:
1243 return map(bin, d[:-1].split(" "))
1273 return map(bin, d[:-1].split(" "))
1244 except:
1274 except:
1245 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1275 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1246 raise
1276 raise
1247
1277
1248 def branches(self, nodes):
1278 def branches(self, nodes):
1249 n = " ".join(map(hex, nodes))
1279 n = " ".join(map(hex, nodes))
1250 d = self.do_cmd("branches", nodes=n).read()
1280 d = self.do_cmd("branches", nodes=n).read()
1251 try:
1281 try:
1252 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1282 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1253 return br
1283 return br
1254 except:
1284 except:
1255 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1285 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1256 raise
1286 raise
1257
1287
1258 def between(self, pairs):
1288 def between(self, pairs):
1259 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1289 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1260 d = self.do_cmd("between", pairs=n).read()
1290 d = self.do_cmd("between", pairs=n).read()
1261 try:
1291 try:
1262 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1292 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1263 return p
1293 return p
1264 except:
1294 except:
1265 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1295 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1266 raise
1296 raise
1267
1297
1268 def changegroup(self, nodes):
1298 def changegroup(self, nodes):
1269 n = " ".join(map(hex, nodes))
1299 n = " ".join(map(hex, nodes))
1270 zd = zlib.decompressobj()
1300 zd = zlib.decompressobj()
1271 f = self.do_cmd("changegroup", roots=n)
1301 f = self.do_cmd("changegroup", roots=n)
1272 bytes = 0
1302 bytes = 0
1273 while 1:
1303 while 1:
1274 d = f.read(4096)
1304 d = f.read(4096)
1275 bytes += len(d)
1305 bytes += len(d)
1276 if not d:
1306 if not d:
1277 yield zd.flush()
1307 yield zd.flush()
1278 break
1308 break
1279 yield zd.decompress(d)
1309 yield zd.decompress(d)
1280 self.ui.note("%d bytes of data transfered\n" % bytes)
1310 self.ui.note("%d bytes of data transfered\n" % bytes)
1281
1311
1282 def repository(ui, path=None, create=0):
1312 def repository(ui, path=None, create=0):
1283 if path and path[:7] == "http://":
1313 if path and path[:7] == "http://":
1284 return remoterepository(ui, path)
1314 return remoterepository(ui, path)
1285 if path and path[:5] == "hg://":
1315 if path and path[:5] == "hg://":
1286 return remoterepository(ui, path.replace("hg://", "http://"))
1316 return remoterepository(ui, path.replace("hg://", "http://"))
1287 if path and path[:11] == "old-http://":
1317 if path and path[:11] == "old-http://":
1288 return localrepository(ui, path.replace("old-http://", "http://"))
1318 return localrepository(ui, path.replace("old-http://", "http://"))
1289 else:
1319 else:
1290 return localrepository(ui, path, create)
1320 return localrepository(ui, path, create)
1291
1321
1292 class httprangereader:
1322 class httprangereader:
1293 def __init__(self, url):
1323 def __init__(self, url):
1294 self.url = url
1324 self.url = url
1295 self.pos = 0
1325 self.pos = 0
1296 def seek(self, pos):
1326 def seek(self, pos):
1297 self.pos = pos
1327 self.pos = pos
1298 def read(self, bytes=None):
1328 def read(self, bytes=None):
1299 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
1329 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
1300 urllib2.install_opener(opener)
1330 urllib2.install_opener(opener)
1301 req = urllib2.Request(self.url)
1331 req = urllib2.Request(self.url)
1302 end = ''
1332 end = ''
1303 if bytes: end = self.pos + bytes
1333 if bytes: end = self.pos + bytes
1304 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
1334 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
1305 f = urllib2.urlopen(req)
1335 f = urllib2.urlopen(req)
1306 return f.read()
1336 return f.read()
General Comments 0
You need to be logged in to leave comments. Login now