##// END OF EJS Templates
addremove: support addremove with explicit paths in subrepos...
Matt Harbison -
r23539:cb42050f default
parent child Browse files
Show More
@@ -1,158 +1,160 b''
1 Subrepositories let you nest external repositories or projects into a
1 Subrepositories let you nest external repositories or projects into a
2 parent Mercurial repository, and make commands operate on them as a
2 parent Mercurial repository, and make commands operate on them as a
3 group.
3 group.
4
4
5 Mercurial currently supports Mercurial, Git, and Subversion
5 Mercurial currently supports Mercurial, Git, and Subversion
6 subrepositories.
6 subrepositories.
7
7
8 Subrepositories are made of three components:
8 Subrepositories are made of three components:
9
9
10 1. Nested repository checkouts. They can appear anywhere in the
10 1. Nested repository checkouts. They can appear anywhere in the
11 parent working directory.
11 parent working directory.
12
12
13 2. Nested repository references. They are defined in ``.hgsub``, which
13 2. Nested repository references. They are defined in ``.hgsub``, which
14 should be placed in the root of working directory, and
14 should be placed in the root of working directory, and
15 tell where the subrepository checkouts come from. Mercurial
15 tell where the subrepository checkouts come from. Mercurial
16 subrepositories are referenced like::
16 subrepositories are referenced like::
17
17
18 path/to/nested = https://example.com/nested/repo/path
18 path/to/nested = https://example.com/nested/repo/path
19
19
20 Git and Subversion subrepos are also supported::
20 Git and Subversion subrepos are also supported::
21
21
22 path/to/nested = [git]git://example.com/nested/repo/path
22 path/to/nested = [git]git://example.com/nested/repo/path
23 path/to/nested = [svn]https://example.com/nested/trunk/path
23 path/to/nested = [svn]https://example.com/nested/trunk/path
24
24
25 where ``path/to/nested`` is the checkout location relatively to the
25 where ``path/to/nested`` is the checkout location relatively to the
26 parent Mercurial root, and ``https://example.com/nested/repo/path``
26 parent Mercurial root, and ``https://example.com/nested/repo/path``
27 is the source repository path. The source can also reference a
27 is the source repository path. The source can also reference a
28 filesystem path.
28 filesystem path.
29
29
30 Note that ``.hgsub`` does not exist by default in Mercurial
30 Note that ``.hgsub`` does not exist by default in Mercurial
31 repositories, you have to create and add it to the parent
31 repositories, you have to create and add it to the parent
32 repository before using subrepositories.
32 repository before using subrepositories.
33
33
34 3. Nested repository states. They are defined in ``.hgsubstate``, which
34 3. Nested repository states. They are defined in ``.hgsubstate``, which
35 is placed in the root of working directory, and
35 is placed in the root of working directory, and
36 capture whatever information is required to restore the
36 capture whatever information is required to restore the
37 subrepositories to the state they were committed in a parent
37 subrepositories to the state they were committed in a parent
38 repository changeset. Mercurial automatically record the nested
38 repository changeset. Mercurial automatically record the nested
39 repositories states when committing in the parent repository.
39 repositories states when committing in the parent repository.
40
40
41 .. note::
41 .. note::
42
42
43 The ``.hgsubstate`` file should not be edited manually.
43 The ``.hgsubstate`` file should not be edited manually.
44
44
45
45
46 Adding a Subrepository
46 Adding a Subrepository
47 ======================
47 ======================
48
48
49 If ``.hgsub`` does not exist, create it and add it to the parent
49 If ``.hgsub`` does not exist, create it and add it to the parent
50 repository. Clone or checkout the external projects where you want it
50 repository. Clone or checkout the external projects where you want it
51 to live in the parent repository. Edit ``.hgsub`` and add the
51 to live in the parent repository. Edit ``.hgsub`` and add the
52 subrepository entry as described above. At this point, the
52 subrepository entry as described above. At this point, the
53 subrepository is tracked and the next commit will record its state in
53 subrepository is tracked and the next commit will record its state in
54 ``.hgsubstate`` and bind it to the committed changeset.
54 ``.hgsubstate`` and bind it to the committed changeset.
55
55
56 Synchronizing a Subrepository
56 Synchronizing a Subrepository
57 =============================
57 =============================
58
58
59 Subrepos do not automatically track the latest changeset of their
59 Subrepos do not automatically track the latest changeset of their
60 sources. Instead, they are updated to the changeset that corresponds
60 sources. Instead, they are updated to the changeset that corresponds
61 with the changeset checked out in the top-level changeset. This is so
61 with the changeset checked out in the top-level changeset. This is so
62 developers always get a consistent set of compatible code and
62 developers always get a consistent set of compatible code and
63 libraries when they update.
63 libraries when they update.
64
64
65 Thus, updating subrepos is a manual process. Simply check out target
65 Thus, updating subrepos is a manual process. Simply check out target
66 subrepo at the desired revision, test in the top-level repo, then
66 subrepo at the desired revision, test in the top-level repo, then
67 commit in the parent repository to record the new combination.
67 commit in the parent repository to record the new combination.
68
68
69 Deleting a Subrepository
69 Deleting a Subrepository
70 ========================
70 ========================
71
71
72 To remove a subrepository from the parent repository, delete its
72 To remove a subrepository from the parent repository, delete its
73 reference from ``.hgsub``, then remove its files.
73 reference from ``.hgsub``, then remove its files.
74
74
75 Interaction with Mercurial Commands
75 Interaction with Mercurial Commands
76 ===================================
76 ===================================
77
77
78 :add: add does not recurse in subrepos unless -S/--subrepos is
78 :add: add does not recurse in subrepos unless -S/--subrepos is
79 specified. However, if you specify the full path of a file in a
79 specified. However, if you specify the full path of a file in a
80 subrepo, it will be added even without -S/--subrepos specified.
80 subrepo, it will be added even without -S/--subrepos specified.
81 Git and Subversion subrepositories are currently silently
81 Git and Subversion subrepositories are currently silently
82 ignored.
82 ignored.
83
83
84 :addremove: addremove does not recurse into subrepos unless
84 :addremove: addremove does not recurse into subrepos unless
85 -S/--subrepos is specified. Git and Subversion subrepositories
85 -S/--subrepos is specified. However, if you specify the full
86 will print a warning and continue.
86 path of a directory in a subrepo, addremove will be performed on
87 it even without -S/--subrepos being specified. Git and
88 Subversion subrepositories will print a warning and continue.
87
89
88 :archive: archive does not recurse in subrepositories unless
90 :archive: archive does not recurse in subrepositories unless
89 -S/--subrepos is specified.
91 -S/--subrepos is specified.
90
92
91 :cat: cat currently only handles exact file matches in subrepos.
93 :cat: cat currently only handles exact file matches in subrepos.
92 Git and Subversion subrepositories are currently ignored.
94 Git and Subversion subrepositories are currently ignored.
93
95
94 :commit: commit creates a consistent snapshot of the state of the
96 :commit: commit creates a consistent snapshot of the state of the
95 entire project and its subrepositories. If any subrepositories
97 entire project and its subrepositories. If any subrepositories
96 have been modified, Mercurial will abort. Mercurial can be made
98 have been modified, Mercurial will abort. Mercurial can be made
97 to instead commit all modified subrepositories by specifying
99 to instead commit all modified subrepositories by specifying
98 -S/--subrepos, or setting "ui.commitsubrepos=True" in a
100 -S/--subrepos, or setting "ui.commitsubrepos=True" in a
99 configuration file (see :hg:`help config`). After there are no
101 configuration file (see :hg:`help config`). After there are no
100 longer any modified subrepositories, it records their state and
102 longer any modified subrepositories, it records their state and
101 finally commits it in the parent repository. The --addremove
103 finally commits it in the parent repository. The --addremove
102 option also honors the -S/--subrepos option. However, Git and
104 option also honors the -S/--subrepos option. However, Git and
103 Subversion subrepositories will print a warning and abort.
105 Subversion subrepositories will print a warning and abort.
104
106
105 :diff: diff does not recurse in subrepos unless -S/--subrepos is
107 :diff: diff does not recurse in subrepos unless -S/--subrepos is
106 specified. Changes are displayed as usual, on the subrepositories
108 specified. Changes are displayed as usual, on the subrepositories
107 elements. Git subrepositories do not support --include/--exclude.
109 elements. Git subrepositories do not support --include/--exclude.
108 Subversion subrepositories are currently silently ignored.
110 Subversion subrepositories are currently silently ignored.
109
111
110 :forget: forget currently only handles exact file matches in subrepos.
112 :forget: forget currently only handles exact file matches in subrepos.
111 Git and Subversion subrepositories are currently silently ignored.
113 Git and Subversion subrepositories are currently silently ignored.
112
114
113 :incoming: incoming does not recurse in subrepos unless -S/--subrepos
115 :incoming: incoming does not recurse in subrepos unless -S/--subrepos
114 is specified. Git and Subversion subrepositories are currently
116 is specified. Git and Subversion subrepositories are currently
115 silently ignored.
117 silently ignored.
116
118
117 :outgoing: outgoing does not recurse in subrepos unless -S/--subrepos
119 :outgoing: outgoing does not recurse in subrepos unless -S/--subrepos
118 is specified. Git and Subversion subrepositories are currently
120 is specified. Git and Subversion subrepositories are currently
119 silently ignored.
121 silently ignored.
120
122
121 :pull: pull is not recursive since it is not clear what to pull prior
123 :pull: pull is not recursive since it is not clear what to pull prior
122 to running :hg:`update`. Listing and retrieving all
124 to running :hg:`update`. Listing and retrieving all
123 subrepositories changes referenced by the parent repository pulled
125 subrepositories changes referenced by the parent repository pulled
124 changesets is expensive at best, impossible in the Subversion
126 changesets is expensive at best, impossible in the Subversion
125 case.
127 case.
126
128
127 :push: Mercurial will automatically push all subrepositories first
129 :push: Mercurial will automatically push all subrepositories first
128 when the parent repository is being pushed. This ensures new
130 when the parent repository is being pushed. This ensures new
129 subrepository changes are available when referenced by top-level
131 subrepository changes are available when referenced by top-level
130 repositories. Push is a no-op for Subversion subrepositories.
132 repositories. Push is a no-op for Subversion subrepositories.
131
133
132 :status: status does not recurse into subrepositories unless
134 :status: status does not recurse into subrepositories unless
133 -S/--subrepos is specified. Subrepository changes are displayed as
135 -S/--subrepos is specified. Subrepository changes are displayed as
134 regular Mercurial changes on the subrepository
136 regular Mercurial changes on the subrepository
135 elements. Subversion subrepositories are currently silently
137 elements. Subversion subrepositories are currently silently
136 ignored.
138 ignored.
137
139
138 :remove: remove does not recurse into subrepositories unless
140 :remove: remove does not recurse into subrepositories unless
139 -S/--subrepos is specified. However, if you specify a file or
141 -S/--subrepos is specified. However, if you specify a file or
140 directory path in a subrepo, it will be removed even without
142 directory path in a subrepo, it will be removed even without
141 -S/--subrepos. Git and Subversion subrepositories are currently
143 -S/--subrepos. Git and Subversion subrepositories are currently
142 silently ignored.
144 silently ignored.
143
145
144 :update: update restores the subrepos in the state they were
146 :update: update restores the subrepos in the state they were
145 originally committed in target changeset. If the recorded
147 originally committed in target changeset. If the recorded
146 changeset is not available in the current subrepository, Mercurial
148 changeset is not available in the current subrepository, Mercurial
147 will pull it in first before updating. This means that updating
149 will pull it in first before updating. This means that updating
148 can require network access when using subrepositories.
150 can require network access when using subrepositories.
149
151
150 Remapping Subrepositories Sources
152 Remapping Subrepositories Sources
151 =================================
153 =================================
152
154
153 A subrepository source location may change during a project life,
155 A subrepository source location may change during a project life,
154 invalidating references stored in the parent repository history. To
156 invalidating references stored in the parent repository history. To
155 fix this, rewriting rules can be defined in parent repository ``hgrc``
157 fix this, rewriting rules can be defined in parent repository ``hgrc``
156 file or in Mercurial configuration. See the ``[subpaths]`` section in
158 file or in Mercurial configuration. See the ``[subpaths]`` section in
157 hgrc(5) for more details.
159 hgrc(5) for more details.
158
160
@@ -1,1087 +1,1095 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 from mercurial.node import nullrev
9 from mercurial.node import nullrev
10 import util, error, osutil, revset, similar, encoding, phases, parsers
10 import util, error, osutil, revset, similar, encoding, phases, parsers
11 import pathutil
11 import pathutil
12 import match as matchmod
12 import match as matchmod
13 import os, errno, re, glob, tempfile
13 import os, errno, re, glob, tempfile
14
14
15 if os.name == 'nt':
15 if os.name == 'nt':
16 import scmwindows as scmplatform
16 import scmwindows as scmplatform
17 else:
17 else:
18 import scmposix as scmplatform
18 import scmposix as scmplatform
19
19
20 systemrcpath = scmplatform.systemrcpath
20 systemrcpath = scmplatform.systemrcpath
21 userrcpath = scmplatform.userrcpath
21 userrcpath = scmplatform.userrcpath
22
22
23 class status(tuple):
23 class status(tuple):
24 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
24 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
25 and 'ignored' properties are only relevant to the working copy.
25 and 'ignored' properties are only relevant to the working copy.
26 '''
26 '''
27
27
28 __slots__ = ()
28 __slots__ = ()
29
29
30 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
30 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
31 clean):
31 clean):
32 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
32 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
33 ignored, clean))
33 ignored, clean))
34
34
35 @property
35 @property
36 def modified(self):
36 def modified(self):
37 '''files that have been modified'''
37 '''files that have been modified'''
38 return self[0]
38 return self[0]
39
39
40 @property
40 @property
41 def added(self):
41 def added(self):
42 '''files that have been added'''
42 '''files that have been added'''
43 return self[1]
43 return self[1]
44
44
45 @property
45 @property
46 def removed(self):
46 def removed(self):
47 '''files that have been removed'''
47 '''files that have been removed'''
48 return self[2]
48 return self[2]
49
49
50 @property
50 @property
51 def deleted(self):
51 def deleted(self):
52 '''files that are in the dirstate, but have been deleted from the
52 '''files that are in the dirstate, but have been deleted from the
53 working copy (aka "missing")
53 working copy (aka "missing")
54 '''
54 '''
55 return self[3]
55 return self[3]
56
56
57 @property
57 @property
58 def unknown(self):
58 def unknown(self):
59 '''files not in the dirstate that are not ignored'''
59 '''files not in the dirstate that are not ignored'''
60 return self[4]
60 return self[4]
61
61
62 @property
62 @property
63 def ignored(self):
63 def ignored(self):
64 '''files not in the dirstate that are ignored (by _dirignore())'''
64 '''files not in the dirstate that are ignored (by _dirignore())'''
65 return self[5]
65 return self[5]
66
66
67 @property
67 @property
68 def clean(self):
68 def clean(self):
69 '''files that have not been modified'''
69 '''files that have not been modified'''
70 return self[6]
70 return self[6]
71
71
72 def __repr__(self, *args, **kwargs):
72 def __repr__(self, *args, **kwargs):
73 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
73 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
74 'unknown=%r, ignored=%r, clean=%r>') % self)
74 'unknown=%r, ignored=%r, clean=%r>') % self)
75
75
76 def itersubrepos(ctx1, ctx2):
76 def itersubrepos(ctx1, ctx2):
77 """find subrepos in ctx1 or ctx2"""
77 """find subrepos in ctx1 or ctx2"""
78 # Create a (subpath, ctx) mapping where we prefer subpaths from
78 # Create a (subpath, ctx) mapping where we prefer subpaths from
79 # ctx1. The subpaths from ctx2 are important when the .hgsub file
79 # ctx1. The subpaths from ctx2 are important when the .hgsub file
80 # has been modified (in ctx2) but not yet committed (in ctx1).
80 # has been modified (in ctx2) but not yet committed (in ctx1).
81 subpaths = dict.fromkeys(ctx2.substate, ctx2)
81 subpaths = dict.fromkeys(ctx2.substate, ctx2)
82 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
82 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
83 for subpath, ctx in sorted(subpaths.iteritems()):
83 for subpath, ctx in sorted(subpaths.iteritems()):
84 yield subpath, ctx.sub(subpath)
84 yield subpath, ctx.sub(subpath)
85
85
86 def nochangesfound(ui, repo, excluded=None):
86 def nochangesfound(ui, repo, excluded=None):
87 '''Report no changes for push/pull, excluded is None or a list of
87 '''Report no changes for push/pull, excluded is None or a list of
88 nodes excluded from the push/pull.
88 nodes excluded from the push/pull.
89 '''
89 '''
90 secretlist = []
90 secretlist = []
91 if excluded:
91 if excluded:
92 for n in excluded:
92 for n in excluded:
93 if n not in repo:
93 if n not in repo:
94 # discovery should not have included the filtered revision,
94 # discovery should not have included the filtered revision,
95 # we have to explicitly exclude it until discovery is cleanup.
95 # we have to explicitly exclude it until discovery is cleanup.
96 continue
96 continue
97 ctx = repo[n]
97 ctx = repo[n]
98 if ctx.phase() >= phases.secret and not ctx.extinct():
98 if ctx.phase() >= phases.secret and not ctx.extinct():
99 secretlist.append(n)
99 secretlist.append(n)
100
100
101 if secretlist:
101 if secretlist:
102 ui.status(_("no changes found (ignored %d secret changesets)\n")
102 ui.status(_("no changes found (ignored %d secret changesets)\n")
103 % len(secretlist))
103 % len(secretlist))
104 else:
104 else:
105 ui.status(_("no changes found\n"))
105 ui.status(_("no changes found\n"))
106
106
107 def checknewlabel(repo, lbl, kind):
107 def checknewlabel(repo, lbl, kind):
108 # Do not use the "kind" parameter in ui output.
108 # Do not use the "kind" parameter in ui output.
109 # It makes strings difficult to translate.
109 # It makes strings difficult to translate.
110 if lbl in ['tip', '.', 'null']:
110 if lbl in ['tip', '.', 'null']:
111 raise util.Abort(_("the name '%s' is reserved") % lbl)
111 raise util.Abort(_("the name '%s' is reserved") % lbl)
112 for c in (':', '\0', '\n', '\r'):
112 for c in (':', '\0', '\n', '\r'):
113 if c in lbl:
113 if c in lbl:
114 raise util.Abort(_("%r cannot be used in a name") % c)
114 raise util.Abort(_("%r cannot be used in a name") % c)
115 try:
115 try:
116 int(lbl)
116 int(lbl)
117 raise util.Abort(_("cannot use an integer as a name"))
117 raise util.Abort(_("cannot use an integer as a name"))
118 except ValueError:
118 except ValueError:
119 pass
119 pass
120
120
121 def checkfilename(f):
121 def checkfilename(f):
122 '''Check that the filename f is an acceptable filename for a tracked file'''
122 '''Check that the filename f is an acceptable filename for a tracked file'''
123 if '\r' in f or '\n' in f:
123 if '\r' in f or '\n' in f:
124 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
124 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
125
125
126 def checkportable(ui, f):
126 def checkportable(ui, f):
127 '''Check if filename f is portable and warn or abort depending on config'''
127 '''Check if filename f is portable and warn or abort depending on config'''
128 checkfilename(f)
128 checkfilename(f)
129 abort, warn = checkportabilityalert(ui)
129 abort, warn = checkportabilityalert(ui)
130 if abort or warn:
130 if abort or warn:
131 msg = util.checkwinfilename(f)
131 msg = util.checkwinfilename(f)
132 if msg:
132 if msg:
133 msg = "%s: %r" % (msg, f)
133 msg = "%s: %r" % (msg, f)
134 if abort:
134 if abort:
135 raise util.Abort(msg)
135 raise util.Abort(msg)
136 ui.warn(_("warning: %s\n") % msg)
136 ui.warn(_("warning: %s\n") % msg)
137
137
138 def checkportabilityalert(ui):
138 def checkportabilityalert(ui):
139 '''check if the user's config requests nothing, a warning, or abort for
139 '''check if the user's config requests nothing, a warning, or abort for
140 non-portable filenames'''
140 non-portable filenames'''
141 val = ui.config('ui', 'portablefilenames', 'warn')
141 val = ui.config('ui', 'portablefilenames', 'warn')
142 lval = val.lower()
142 lval = val.lower()
143 bval = util.parsebool(val)
143 bval = util.parsebool(val)
144 abort = os.name == 'nt' or lval == 'abort'
144 abort = os.name == 'nt' or lval == 'abort'
145 warn = bval or lval == 'warn'
145 warn = bval or lval == 'warn'
146 if bval is None and not (warn or abort or lval == 'ignore'):
146 if bval is None and not (warn or abort or lval == 'ignore'):
147 raise error.ConfigError(
147 raise error.ConfigError(
148 _("ui.portablefilenames value is invalid ('%s')") % val)
148 _("ui.portablefilenames value is invalid ('%s')") % val)
149 return abort, warn
149 return abort, warn
150
150
151 class casecollisionauditor(object):
151 class casecollisionauditor(object):
152 def __init__(self, ui, abort, dirstate):
152 def __init__(self, ui, abort, dirstate):
153 self._ui = ui
153 self._ui = ui
154 self._abort = abort
154 self._abort = abort
155 allfiles = '\0'.join(dirstate._map)
155 allfiles = '\0'.join(dirstate._map)
156 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
156 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
157 self._dirstate = dirstate
157 self._dirstate = dirstate
158 # The purpose of _newfiles is so that we don't complain about
158 # The purpose of _newfiles is so that we don't complain about
159 # case collisions if someone were to call this object with the
159 # case collisions if someone were to call this object with the
160 # same filename twice.
160 # same filename twice.
161 self._newfiles = set()
161 self._newfiles = set()
162
162
163 def __call__(self, f):
163 def __call__(self, f):
164 if f in self._newfiles:
164 if f in self._newfiles:
165 return
165 return
166 fl = encoding.lower(f)
166 fl = encoding.lower(f)
167 if fl in self._loweredfiles and f not in self._dirstate:
167 if fl in self._loweredfiles and f not in self._dirstate:
168 msg = _('possible case-folding collision for %s') % f
168 msg = _('possible case-folding collision for %s') % f
169 if self._abort:
169 if self._abort:
170 raise util.Abort(msg)
170 raise util.Abort(msg)
171 self._ui.warn(_("warning: %s\n") % msg)
171 self._ui.warn(_("warning: %s\n") % msg)
172 self._loweredfiles.add(fl)
172 self._loweredfiles.add(fl)
173 self._newfiles.add(f)
173 self._newfiles.add(f)
174
174
175 class abstractvfs(object):
175 class abstractvfs(object):
176 """Abstract base class; cannot be instantiated"""
176 """Abstract base class; cannot be instantiated"""
177
177
178 def __init__(self, *args, **kwargs):
178 def __init__(self, *args, **kwargs):
179 '''Prevent instantiation; don't call this from subclasses.'''
179 '''Prevent instantiation; don't call this from subclasses.'''
180 raise NotImplementedError('attempted instantiating ' + str(type(self)))
180 raise NotImplementedError('attempted instantiating ' + str(type(self)))
181
181
182 def tryread(self, path):
182 def tryread(self, path):
183 '''gracefully return an empty string for missing files'''
183 '''gracefully return an empty string for missing files'''
184 try:
184 try:
185 return self.read(path)
185 return self.read(path)
186 except IOError, inst:
186 except IOError, inst:
187 if inst.errno != errno.ENOENT:
187 if inst.errno != errno.ENOENT:
188 raise
188 raise
189 return ""
189 return ""
190
190
191 def tryreadlines(self, path, mode='rb'):
191 def tryreadlines(self, path, mode='rb'):
192 '''gracefully return an empty array for missing files'''
192 '''gracefully return an empty array for missing files'''
193 try:
193 try:
194 return self.readlines(path, mode=mode)
194 return self.readlines(path, mode=mode)
195 except IOError, inst:
195 except IOError, inst:
196 if inst.errno != errno.ENOENT:
196 if inst.errno != errno.ENOENT:
197 raise
197 raise
198 return []
198 return []
199
199
200 def open(self, path, mode="r", text=False, atomictemp=False,
200 def open(self, path, mode="r", text=False, atomictemp=False,
201 notindexed=False):
201 notindexed=False):
202 '''Open ``path`` file, which is relative to vfs root.
202 '''Open ``path`` file, which is relative to vfs root.
203
203
204 Newly created directories are marked as "not to be indexed by
204 Newly created directories are marked as "not to be indexed by
205 the content indexing service", if ``notindexed`` is specified
205 the content indexing service", if ``notindexed`` is specified
206 for "write" mode access.
206 for "write" mode access.
207 '''
207 '''
208 self.open = self.__call__
208 self.open = self.__call__
209 return self.__call__(path, mode, text, atomictemp, notindexed)
209 return self.__call__(path, mode, text, atomictemp, notindexed)
210
210
211 def read(self, path):
211 def read(self, path):
212 fp = self(path, 'rb')
212 fp = self(path, 'rb')
213 try:
213 try:
214 return fp.read()
214 return fp.read()
215 finally:
215 finally:
216 fp.close()
216 fp.close()
217
217
218 def readlines(self, path, mode='rb'):
218 def readlines(self, path, mode='rb'):
219 fp = self(path, mode=mode)
219 fp = self(path, mode=mode)
220 try:
220 try:
221 return fp.readlines()
221 return fp.readlines()
222 finally:
222 finally:
223 fp.close()
223 fp.close()
224
224
225 def write(self, path, data):
225 def write(self, path, data):
226 fp = self(path, 'wb')
226 fp = self(path, 'wb')
227 try:
227 try:
228 return fp.write(data)
228 return fp.write(data)
229 finally:
229 finally:
230 fp.close()
230 fp.close()
231
231
232 def writelines(self, path, data, mode='wb', notindexed=False):
232 def writelines(self, path, data, mode='wb', notindexed=False):
233 fp = self(path, mode=mode, notindexed=notindexed)
233 fp = self(path, mode=mode, notindexed=notindexed)
234 try:
234 try:
235 return fp.writelines(data)
235 return fp.writelines(data)
236 finally:
236 finally:
237 fp.close()
237 fp.close()
238
238
239 def append(self, path, data):
239 def append(self, path, data):
240 fp = self(path, 'ab')
240 fp = self(path, 'ab')
241 try:
241 try:
242 return fp.write(data)
242 return fp.write(data)
243 finally:
243 finally:
244 fp.close()
244 fp.close()
245
245
246 def chmod(self, path, mode):
246 def chmod(self, path, mode):
247 return os.chmod(self.join(path), mode)
247 return os.chmod(self.join(path), mode)
248
248
249 def exists(self, path=None):
249 def exists(self, path=None):
250 return os.path.exists(self.join(path))
250 return os.path.exists(self.join(path))
251
251
252 def fstat(self, fp):
252 def fstat(self, fp):
253 return util.fstat(fp)
253 return util.fstat(fp)
254
254
255 def isdir(self, path=None):
255 def isdir(self, path=None):
256 return os.path.isdir(self.join(path))
256 return os.path.isdir(self.join(path))
257
257
258 def isfile(self, path=None):
258 def isfile(self, path=None):
259 return os.path.isfile(self.join(path))
259 return os.path.isfile(self.join(path))
260
260
261 def islink(self, path=None):
261 def islink(self, path=None):
262 return os.path.islink(self.join(path))
262 return os.path.islink(self.join(path))
263
263
264 def lexists(self, path=None):
264 def lexists(self, path=None):
265 return os.path.lexists(self.join(path))
265 return os.path.lexists(self.join(path))
266
266
267 def lstat(self, path=None):
267 def lstat(self, path=None):
268 return os.lstat(self.join(path))
268 return os.lstat(self.join(path))
269
269
270 def listdir(self, path=None):
270 def listdir(self, path=None):
271 return os.listdir(self.join(path))
271 return os.listdir(self.join(path))
272
272
273 def makedir(self, path=None, notindexed=True):
273 def makedir(self, path=None, notindexed=True):
274 return util.makedir(self.join(path), notindexed)
274 return util.makedir(self.join(path), notindexed)
275
275
276 def makedirs(self, path=None, mode=None):
276 def makedirs(self, path=None, mode=None):
277 return util.makedirs(self.join(path), mode)
277 return util.makedirs(self.join(path), mode)
278
278
279 def makelock(self, info, path):
279 def makelock(self, info, path):
280 return util.makelock(info, self.join(path))
280 return util.makelock(info, self.join(path))
281
281
282 def mkdir(self, path=None):
282 def mkdir(self, path=None):
283 return os.mkdir(self.join(path))
283 return os.mkdir(self.join(path))
284
284
285 def mkstemp(self, suffix='', prefix='tmp', dir=None, text=False):
285 def mkstemp(self, suffix='', prefix='tmp', dir=None, text=False):
286 fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
286 fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
287 dir=self.join(dir), text=text)
287 dir=self.join(dir), text=text)
288 dname, fname = util.split(name)
288 dname, fname = util.split(name)
289 if dir:
289 if dir:
290 return fd, os.path.join(dir, fname)
290 return fd, os.path.join(dir, fname)
291 else:
291 else:
292 return fd, fname
292 return fd, fname
293
293
294 def readdir(self, path=None, stat=None, skip=None):
294 def readdir(self, path=None, stat=None, skip=None):
295 return osutil.listdir(self.join(path), stat, skip)
295 return osutil.listdir(self.join(path), stat, skip)
296
296
297 def readlock(self, path):
297 def readlock(self, path):
298 return util.readlock(self.join(path))
298 return util.readlock(self.join(path))
299
299
300 def rename(self, src, dst):
300 def rename(self, src, dst):
301 return util.rename(self.join(src), self.join(dst))
301 return util.rename(self.join(src), self.join(dst))
302
302
303 def readlink(self, path):
303 def readlink(self, path):
304 return os.readlink(self.join(path))
304 return os.readlink(self.join(path))
305
305
306 def setflags(self, path, l, x):
306 def setflags(self, path, l, x):
307 return util.setflags(self.join(path), l, x)
307 return util.setflags(self.join(path), l, x)
308
308
309 def stat(self, path=None):
309 def stat(self, path=None):
310 return os.stat(self.join(path))
310 return os.stat(self.join(path))
311
311
312 def unlink(self, path=None):
312 def unlink(self, path=None):
313 return util.unlink(self.join(path))
313 return util.unlink(self.join(path))
314
314
315 def unlinkpath(self, path=None, ignoremissing=False):
315 def unlinkpath(self, path=None, ignoremissing=False):
316 return util.unlinkpath(self.join(path), ignoremissing)
316 return util.unlinkpath(self.join(path), ignoremissing)
317
317
318 def utime(self, path=None, t=None):
318 def utime(self, path=None, t=None):
319 return os.utime(self.join(path), t)
319 return os.utime(self.join(path), t)
320
320
321 class vfs(abstractvfs):
321 class vfs(abstractvfs):
322 '''Operate files relative to a base directory
322 '''Operate files relative to a base directory
323
323
324 This class is used to hide the details of COW semantics and
324 This class is used to hide the details of COW semantics and
325 remote file access from higher level code.
325 remote file access from higher level code.
326 '''
326 '''
327 def __init__(self, base, audit=True, expandpath=False, realpath=False):
327 def __init__(self, base, audit=True, expandpath=False, realpath=False):
328 if expandpath:
328 if expandpath:
329 base = util.expandpath(base)
329 base = util.expandpath(base)
330 if realpath:
330 if realpath:
331 base = os.path.realpath(base)
331 base = os.path.realpath(base)
332 self.base = base
332 self.base = base
333 self._setmustaudit(audit)
333 self._setmustaudit(audit)
334 self.createmode = None
334 self.createmode = None
335 self._trustnlink = None
335 self._trustnlink = None
336
336
337 def _getmustaudit(self):
337 def _getmustaudit(self):
338 return self._audit
338 return self._audit
339
339
340 def _setmustaudit(self, onoff):
340 def _setmustaudit(self, onoff):
341 self._audit = onoff
341 self._audit = onoff
342 if onoff:
342 if onoff:
343 self.audit = pathutil.pathauditor(self.base)
343 self.audit = pathutil.pathauditor(self.base)
344 else:
344 else:
345 self.audit = util.always
345 self.audit = util.always
346
346
347 mustaudit = property(_getmustaudit, _setmustaudit)
347 mustaudit = property(_getmustaudit, _setmustaudit)
348
348
349 @util.propertycache
349 @util.propertycache
350 def _cansymlink(self):
350 def _cansymlink(self):
351 return util.checklink(self.base)
351 return util.checklink(self.base)
352
352
353 @util.propertycache
353 @util.propertycache
354 def _chmod(self):
354 def _chmod(self):
355 return util.checkexec(self.base)
355 return util.checkexec(self.base)
356
356
357 def _fixfilemode(self, name):
357 def _fixfilemode(self, name):
358 if self.createmode is None or not self._chmod:
358 if self.createmode is None or not self._chmod:
359 return
359 return
360 os.chmod(name, self.createmode & 0666)
360 os.chmod(name, self.createmode & 0666)
361
361
362 def __call__(self, path, mode="r", text=False, atomictemp=False,
362 def __call__(self, path, mode="r", text=False, atomictemp=False,
363 notindexed=False):
363 notindexed=False):
364 '''Open ``path`` file, which is relative to vfs root.
364 '''Open ``path`` file, which is relative to vfs root.
365
365
366 Newly created directories are marked as "not to be indexed by
366 Newly created directories are marked as "not to be indexed by
367 the content indexing service", if ``notindexed`` is specified
367 the content indexing service", if ``notindexed`` is specified
368 for "write" mode access.
368 for "write" mode access.
369 '''
369 '''
370 if self._audit:
370 if self._audit:
371 r = util.checkosfilename(path)
371 r = util.checkosfilename(path)
372 if r:
372 if r:
373 raise util.Abort("%s: %r" % (r, path))
373 raise util.Abort("%s: %r" % (r, path))
374 self.audit(path)
374 self.audit(path)
375 f = self.join(path)
375 f = self.join(path)
376
376
377 if not text and "b" not in mode:
377 if not text and "b" not in mode:
378 mode += "b" # for that other OS
378 mode += "b" # for that other OS
379
379
380 nlink = -1
380 nlink = -1
381 if mode not in ('r', 'rb'):
381 if mode not in ('r', 'rb'):
382 dirname, basename = util.split(f)
382 dirname, basename = util.split(f)
383 # If basename is empty, then the path is malformed because it points
383 # If basename is empty, then the path is malformed because it points
384 # to a directory. Let the posixfile() call below raise IOError.
384 # to a directory. Let the posixfile() call below raise IOError.
385 if basename:
385 if basename:
386 if atomictemp:
386 if atomictemp:
387 util.ensuredirs(dirname, self.createmode, notindexed)
387 util.ensuredirs(dirname, self.createmode, notindexed)
388 return util.atomictempfile(f, mode, self.createmode)
388 return util.atomictempfile(f, mode, self.createmode)
389 try:
389 try:
390 if 'w' in mode:
390 if 'w' in mode:
391 util.unlink(f)
391 util.unlink(f)
392 nlink = 0
392 nlink = 0
393 else:
393 else:
394 # nlinks() may behave differently for files on Windows
394 # nlinks() may behave differently for files on Windows
395 # shares if the file is open.
395 # shares if the file is open.
396 fd = util.posixfile(f)
396 fd = util.posixfile(f)
397 nlink = util.nlinks(f)
397 nlink = util.nlinks(f)
398 if nlink < 1:
398 if nlink < 1:
399 nlink = 2 # force mktempcopy (issue1922)
399 nlink = 2 # force mktempcopy (issue1922)
400 fd.close()
400 fd.close()
401 except (OSError, IOError), e:
401 except (OSError, IOError), e:
402 if e.errno != errno.ENOENT:
402 if e.errno != errno.ENOENT:
403 raise
403 raise
404 nlink = 0
404 nlink = 0
405 util.ensuredirs(dirname, self.createmode, notindexed)
405 util.ensuredirs(dirname, self.createmode, notindexed)
406 if nlink > 0:
406 if nlink > 0:
407 if self._trustnlink is None:
407 if self._trustnlink is None:
408 self._trustnlink = nlink > 1 or util.checknlink(f)
408 self._trustnlink = nlink > 1 or util.checknlink(f)
409 if nlink > 1 or not self._trustnlink:
409 if nlink > 1 or not self._trustnlink:
410 util.rename(util.mktempcopy(f), f)
410 util.rename(util.mktempcopy(f), f)
411 fp = util.posixfile(f, mode)
411 fp = util.posixfile(f, mode)
412 if nlink == 0:
412 if nlink == 0:
413 self._fixfilemode(f)
413 self._fixfilemode(f)
414 return fp
414 return fp
415
415
416 def symlink(self, src, dst):
416 def symlink(self, src, dst):
417 self.audit(dst)
417 self.audit(dst)
418 linkname = self.join(dst)
418 linkname = self.join(dst)
419 try:
419 try:
420 os.unlink(linkname)
420 os.unlink(linkname)
421 except OSError:
421 except OSError:
422 pass
422 pass
423
423
424 util.ensuredirs(os.path.dirname(linkname), self.createmode)
424 util.ensuredirs(os.path.dirname(linkname), self.createmode)
425
425
426 if self._cansymlink:
426 if self._cansymlink:
427 try:
427 try:
428 os.symlink(src, linkname)
428 os.symlink(src, linkname)
429 except OSError, err:
429 except OSError, err:
430 raise OSError(err.errno, _('could not symlink to %r: %s') %
430 raise OSError(err.errno, _('could not symlink to %r: %s') %
431 (src, err.strerror), linkname)
431 (src, err.strerror), linkname)
432 else:
432 else:
433 self.write(dst, src)
433 self.write(dst, src)
434
434
435 def join(self, path):
435 def join(self, path):
436 if path:
436 if path:
437 return os.path.join(self.base, path)
437 return os.path.join(self.base, path)
438 else:
438 else:
439 return self.base
439 return self.base
440
440
441 opener = vfs
441 opener = vfs
442
442
443 class auditvfs(object):
443 class auditvfs(object):
444 def __init__(self, vfs):
444 def __init__(self, vfs):
445 self.vfs = vfs
445 self.vfs = vfs
446
446
447 def _getmustaudit(self):
447 def _getmustaudit(self):
448 return self.vfs.mustaudit
448 return self.vfs.mustaudit
449
449
450 def _setmustaudit(self, onoff):
450 def _setmustaudit(self, onoff):
451 self.vfs.mustaudit = onoff
451 self.vfs.mustaudit = onoff
452
452
453 mustaudit = property(_getmustaudit, _setmustaudit)
453 mustaudit = property(_getmustaudit, _setmustaudit)
454
454
455 class filtervfs(abstractvfs, auditvfs):
455 class filtervfs(abstractvfs, auditvfs):
456 '''Wrapper vfs for filtering filenames with a function.'''
456 '''Wrapper vfs for filtering filenames with a function.'''
457
457
458 def __init__(self, vfs, filter):
458 def __init__(self, vfs, filter):
459 auditvfs.__init__(self, vfs)
459 auditvfs.__init__(self, vfs)
460 self._filter = filter
460 self._filter = filter
461
461
462 def __call__(self, path, *args, **kwargs):
462 def __call__(self, path, *args, **kwargs):
463 return self.vfs(self._filter(path), *args, **kwargs)
463 return self.vfs(self._filter(path), *args, **kwargs)
464
464
465 def join(self, path):
465 def join(self, path):
466 if path:
466 if path:
467 return self.vfs.join(self._filter(path))
467 return self.vfs.join(self._filter(path))
468 else:
468 else:
469 return self.vfs.join(path)
469 return self.vfs.join(path)
470
470
471 filteropener = filtervfs
471 filteropener = filtervfs
472
472
473 class readonlyvfs(abstractvfs, auditvfs):
473 class readonlyvfs(abstractvfs, auditvfs):
474 '''Wrapper vfs preventing any writing.'''
474 '''Wrapper vfs preventing any writing.'''
475
475
476 def __init__(self, vfs):
476 def __init__(self, vfs):
477 auditvfs.__init__(self, vfs)
477 auditvfs.__init__(self, vfs)
478
478
479 def __call__(self, path, mode='r', *args, **kw):
479 def __call__(self, path, mode='r', *args, **kw):
480 if mode not in ('r', 'rb'):
480 if mode not in ('r', 'rb'):
481 raise util.Abort('this vfs is read only')
481 raise util.Abort('this vfs is read only')
482 return self.vfs(path, mode, *args, **kw)
482 return self.vfs(path, mode, *args, **kw)
483
483
484
484
485 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
485 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
486 '''yield every hg repository under path, always recursively.
486 '''yield every hg repository under path, always recursively.
487 The recurse flag will only control recursion into repo working dirs'''
487 The recurse flag will only control recursion into repo working dirs'''
488 def errhandler(err):
488 def errhandler(err):
489 if err.filename == path:
489 if err.filename == path:
490 raise err
490 raise err
491 samestat = getattr(os.path, 'samestat', None)
491 samestat = getattr(os.path, 'samestat', None)
492 if followsym and samestat is not None:
492 if followsym and samestat is not None:
493 def adddir(dirlst, dirname):
493 def adddir(dirlst, dirname):
494 match = False
494 match = False
495 dirstat = os.stat(dirname)
495 dirstat = os.stat(dirname)
496 for lstdirstat in dirlst:
496 for lstdirstat in dirlst:
497 if samestat(dirstat, lstdirstat):
497 if samestat(dirstat, lstdirstat):
498 match = True
498 match = True
499 break
499 break
500 if not match:
500 if not match:
501 dirlst.append(dirstat)
501 dirlst.append(dirstat)
502 return not match
502 return not match
503 else:
503 else:
504 followsym = False
504 followsym = False
505
505
506 if (seen_dirs is None) and followsym:
506 if (seen_dirs is None) and followsym:
507 seen_dirs = []
507 seen_dirs = []
508 adddir(seen_dirs, path)
508 adddir(seen_dirs, path)
509 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
509 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
510 dirs.sort()
510 dirs.sort()
511 if '.hg' in dirs:
511 if '.hg' in dirs:
512 yield root # found a repository
512 yield root # found a repository
513 qroot = os.path.join(root, '.hg', 'patches')
513 qroot = os.path.join(root, '.hg', 'patches')
514 if os.path.isdir(os.path.join(qroot, '.hg')):
514 if os.path.isdir(os.path.join(qroot, '.hg')):
515 yield qroot # we have a patch queue repo here
515 yield qroot # we have a patch queue repo here
516 if recurse:
516 if recurse:
517 # avoid recursing inside the .hg directory
517 # avoid recursing inside the .hg directory
518 dirs.remove('.hg')
518 dirs.remove('.hg')
519 else:
519 else:
520 dirs[:] = [] # don't descend further
520 dirs[:] = [] # don't descend further
521 elif followsym:
521 elif followsym:
522 newdirs = []
522 newdirs = []
523 for d in dirs:
523 for d in dirs:
524 fname = os.path.join(root, d)
524 fname = os.path.join(root, d)
525 if adddir(seen_dirs, fname):
525 if adddir(seen_dirs, fname):
526 if os.path.islink(fname):
526 if os.path.islink(fname):
527 for hgname in walkrepos(fname, True, seen_dirs):
527 for hgname in walkrepos(fname, True, seen_dirs):
528 yield hgname
528 yield hgname
529 else:
529 else:
530 newdirs.append(d)
530 newdirs.append(d)
531 dirs[:] = newdirs
531 dirs[:] = newdirs
532
532
533 def osrcpath():
533 def osrcpath():
534 '''return default os-specific hgrc search path'''
534 '''return default os-specific hgrc search path'''
535 path = []
535 path = []
536 defaultpath = os.path.join(util.datapath, 'default.d')
536 defaultpath = os.path.join(util.datapath, 'default.d')
537 if os.path.isdir(defaultpath):
537 if os.path.isdir(defaultpath):
538 for f, kind in osutil.listdir(defaultpath):
538 for f, kind in osutil.listdir(defaultpath):
539 if f.endswith('.rc'):
539 if f.endswith('.rc'):
540 path.append(os.path.join(defaultpath, f))
540 path.append(os.path.join(defaultpath, f))
541 path.extend(systemrcpath())
541 path.extend(systemrcpath())
542 path.extend(userrcpath())
542 path.extend(userrcpath())
543 path = [os.path.normpath(f) for f in path]
543 path = [os.path.normpath(f) for f in path]
544 return path
544 return path
545
545
546 _rcpath = None
546 _rcpath = None
547
547
548 def rcpath():
548 def rcpath():
549 '''return hgrc search path. if env var HGRCPATH is set, use it.
549 '''return hgrc search path. if env var HGRCPATH is set, use it.
550 for each item in path, if directory, use files ending in .rc,
550 for each item in path, if directory, use files ending in .rc,
551 else use item.
551 else use item.
552 make HGRCPATH empty to only look in .hg/hgrc of current repo.
552 make HGRCPATH empty to only look in .hg/hgrc of current repo.
553 if no HGRCPATH, use default os-specific path.'''
553 if no HGRCPATH, use default os-specific path.'''
554 global _rcpath
554 global _rcpath
555 if _rcpath is None:
555 if _rcpath is None:
556 if 'HGRCPATH' in os.environ:
556 if 'HGRCPATH' in os.environ:
557 _rcpath = []
557 _rcpath = []
558 for p in os.environ['HGRCPATH'].split(os.pathsep):
558 for p in os.environ['HGRCPATH'].split(os.pathsep):
559 if not p:
559 if not p:
560 continue
560 continue
561 p = util.expandpath(p)
561 p = util.expandpath(p)
562 if os.path.isdir(p):
562 if os.path.isdir(p):
563 for f, kind in osutil.listdir(p):
563 for f, kind in osutil.listdir(p):
564 if f.endswith('.rc'):
564 if f.endswith('.rc'):
565 _rcpath.append(os.path.join(p, f))
565 _rcpath.append(os.path.join(p, f))
566 else:
566 else:
567 _rcpath.append(p)
567 _rcpath.append(p)
568 else:
568 else:
569 _rcpath = osrcpath()
569 _rcpath = osrcpath()
570 return _rcpath
570 return _rcpath
571
571
572 def revsingle(repo, revspec, default='.'):
572 def revsingle(repo, revspec, default='.'):
573 if not revspec and revspec != 0:
573 if not revspec and revspec != 0:
574 return repo[default]
574 return repo[default]
575
575
576 l = revrange(repo, [revspec])
576 l = revrange(repo, [revspec])
577 if not l:
577 if not l:
578 raise util.Abort(_('empty revision set'))
578 raise util.Abort(_('empty revision set'))
579 return repo[l.last()]
579 return repo[l.last()]
580
580
581 def revpair(repo, revs):
581 def revpair(repo, revs):
582 if not revs:
582 if not revs:
583 return repo.dirstate.p1(), None
583 return repo.dirstate.p1(), None
584
584
585 l = revrange(repo, revs)
585 l = revrange(repo, revs)
586
586
587 if not l:
587 if not l:
588 first = second = None
588 first = second = None
589 elif l.isascending():
589 elif l.isascending():
590 first = l.min()
590 first = l.min()
591 second = l.max()
591 second = l.max()
592 elif l.isdescending():
592 elif l.isdescending():
593 first = l.max()
593 first = l.max()
594 second = l.min()
594 second = l.min()
595 else:
595 else:
596 first = l.first()
596 first = l.first()
597 second = l.last()
597 second = l.last()
598
598
599 if first is None:
599 if first is None:
600 raise util.Abort(_('empty revision range'))
600 raise util.Abort(_('empty revision range'))
601
601
602 if first == second and len(revs) == 1 and _revrangesep not in revs[0]:
602 if first == second and len(revs) == 1 and _revrangesep not in revs[0]:
603 return repo.lookup(first), None
603 return repo.lookup(first), None
604
604
605 return repo.lookup(first), repo.lookup(second)
605 return repo.lookup(first), repo.lookup(second)
606
606
607 _revrangesep = ':'
607 _revrangesep = ':'
608
608
609 def revrange(repo, revs):
609 def revrange(repo, revs):
610 """Yield revision as strings from a list of revision specifications."""
610 """Yield revision as strings from a list of revision specifications."""
611
611
612 def revfix(repo, val, defval):
612 def revfix(repo, val, defval):
613 if not val and val != 0 and defval is not None:
613 if not val and val != 0 and defval is not None:
614 return defval
614 return defval
615 return repo[val].rev()
615 return repo[val].rev()
616
616
617 seen, l = set(), revset.baseset([])
617 seen, l = set(), revset.baseset([])
618 for spec in revs:
618 for spec in revs:
619 if l and not seen:
619 if l and not seen:
620 seen = set(l)
620 seen = set(l)
621 # attempt to parse old-style ranges first to deal with
621 # attempt to parse old-style ranges first to deal with
622 # things like old-tag which contain query metacharacters
622 # things like old-tag which contain query metacharacters
623 try:
623 try:
624 if isinstance(spec, int):
624 if isinstance(spec, int):
625 seen.add(spec)
625 seen.add(spec)
626 l = l + revset.baseset([spec])
626 l = l + revset.baseset([spec])
627 continue
627 continue
628
628
629 if _revrangesep in spec:
629 if _revrangesep in spec:
630 start, end = spec.split(_revrangesep, 1)
630 start, end = spec.split(_revrangesep, 1)
631 start = revfix(repo, start, 0)
631 start = revfix(repo, start, 0)
632 end = revfix(repo, end, len(repo) - 1)
632 end = revfix(repo, end, len(repo) - 1)
633 if end == nullrev and start < 0:
633 if end == nullrev and start < 0:
634 start = nullrev
634 start = nullrev
635 rangeiter = repo.changelog.revs(start, end)
635 rangeiter = repo.changelog.revs(start, end)
636 if not seen and not l:
636 if not seen and not l:
637 # by far the most common case: revs = ["-1:0"]
637 # by far the most common case: revs = ["-1:0"]
638 l = revset.baseset(rangeiter)
638 l = revset.baseset(rangeiter)
639 # defer syncing seen until next iteration
639 # defer syncing seen until next iteration
640 continue
640 continue
641 newrevs = set(rangeiter)
641 newrevs = set(rangeiter)
642 if seen:
642 if seen:
643 newrevs.difference_update(seen)
643 newrevs.difference_update(seen)
644 seen.update(newrevs)
644 seen.update(newrevs)
645 else:
645 else:
646 seen = newrevs
646 seen = newrevs
647 l = l + revset.baseset(sorted(newrevs, reverse=start > end))
647 l = l + revset.baseset(sorted(newrevs, reverse=start > end))
648 continue
648 continue
649 elif spec and spec in repo: # single unquoted rev
649 elif spec and spec in repo: # single unquoted rev
650 rev = revfix(repo, spec, None)
650 rev = revfix(repo, spec, None)
651 if rev in seen:
651 if rev in seen:
652 continue
652 continue
653 seen.add(rev)
653 seen.add(rev)
654 l = l + revset.baseset([rev])
654 l = l + revset.baseset([rev])
655 continue
655 continue
656 except error.RepoLookupError:
656 except error.RepoLookupError:
657 pass
657 pass
658
658
659 # fall through to new-style queries if old-style fails
659 # fall through to new-style queries if old-style fails
660 m = revset.match(repo.ui, spec, repo)
660 m = revset.match(repo.ui, spec, repo)
661 if seen or l:
661 if seen or l:
662 dl = [r for r in m(repo, revset.spanset(repo)) if r not in seen]
662 dl = [r for r in m(repo, revset.spanset(repo)) if r not in seen]
663 l = l + revset.baseset(dl)
663 l = l + revset.baseset(dl)
664 seen.update(dl)
664 seen.update(dl)
665 else:
665 else:
666 l = m(repo, revset.spanset(repo))
666 l = m(repo, revset.spanset(repo))
667
667
668 return l
668 return l
669
669
670 def expandpats(pats):
670 def expandpats(pats):
671 '''Expand bare globs when running on windows.
671 '''Expand bare globs when running on windows.
672 On posix we assume it already has already been done by sh.'''
672 On posix we assume it already has already been done by sh.'''
673 if not util.expandglobs:
673 if not util.expandglobs:
674 return list(pats)
674 return list(pats)
675 ret = []
675 ret = []
676 for kindpat in pats:
676 for kindpat in pats:
677 kind, pat = matchmod._patsplit(kindpat, None)
677 kind, pat = matchmod._patsplit(kindpat, None)
678 if kind is None:
678 if kind is None:
679 try:
679 try:
680 globbed = glob.glob(pat)
680 globbed = glob.glob(pat)
681 except re.error:
681 except re.error:
682 globbed = [pat]
682 globbed = [pat]
683 if globbed:
683 if globbed:
684 ret.extend(globbed)
684 ret.extend(globbed)
685 continue
685 continue
686 ret.append(kindpat)
686 ret.append(kindpat)
687 return ret
687 return ret
688
688
689 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
689 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
690 '''Return a matcher and the patterns that were used.
690 '''Return a matcher and the patterns that were used.
691 The matcher will warn about bad matches.'''
691 The matcher will warn about bad matches.'''
692 if pats == ("",):
692 if pats == ("",):
693 pats = []
693 pats = []
694 if not globbed and default == 'relpath':
694 if not globbed and default == 'relpath':
695 pats = expandpats(pats or [])
695 pats = expandpats(pats or [])
696
696
697 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
697 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
698 default)
698 default)
699 def badfn(f, msg):
699 def badfn(f, msg):
700 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
700 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
701 m.bad = badfn
701 m.bad = badfn
702 return m, pats
702 return m, pats
703
703
704 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
704 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
705 '''Return a matcher that will warn about bad matches.'''
705 '''Return a matcher that will warn about bad matches.'''
706 return matchandpats(ctx, pats, opts, globbed, default)[0]
706 return matchandpats(ctx, pats, opts, globbed, default)[0]
707
707
708 def matchall(repo):
708 def matchall(repo):
709 '''Return a matcher that will efficiently match everything.'''
709 '''Return a matcher that will efficiently match everything.'''
710 return matchmod.always(repo.root, repo.getcwd())
710 return matchmod.always(repo.root, repo.getcwd())
711
711
712 def matchfiles(repo, files):
712 def matchfiles(repo, files):
713 '''Return a matcher that will efficiently match exactly these files.'''
713 '''Return a matcher that will efficiently match exactly these files.'''
714 return matchmod.exact(repo.root, repo.getcwd(), files)
714 return matchmod.exact(repo.root, repo.getcwd(), files)
715
715
716 def addremove(repo, matcher, prefix, opts={}, dry_run=None, similarity=None):
716 def addremove(repo, matcher, prefix, opts={}, dry_run=None, similarity=None):
717 m = matcher
717 m = matcher
718 if dry_run is None:
718 if dry_run is None:
719 dry_run = opts.get('dry_run')
719 dry_run = opts.get('dry_run')
720 if similarity is None:
720 if similarity is None:
721 similarity = float(opts.get('similarity') or 0)
721 similarity = float(opts.get('similarity') or 0)
722
722
723 ret = 0
723 ret = 0
724 join = lambda f: os.path.join(prefix, f)
724 join = lambda f: os.path.join(prefix, f)
725
725
726 def matchessubrepo(matcher, subpath):
727 if matcher.exact(subpath):
728 return True
729 for f in matcher.files():
730 if f.startswith(subpath):
731 return True
732 return False
733
726 wctx = repo[None]
734 wctx = repo[None]
727 for subpath in sorted(wctx.substate):
735 for subpath in sorted(wctx.substate):
728 if opts.get('subrepos'):
736 if opts.get('subrepos') or matchessubrepo(m, subpath):
729 sub = wctx.sub(subpath)
737 sub = wctx.sub(subpath)
730 try:
738 try:
731 submatch = matchmod.narrowmatcher(subpath, m)
739 submatch = matchmod.narrowmatcher(subpath, m)
732 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
740 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
733 ret = 1
741 ret = 1
734 except error.LookupError:
742 except error.LookupError:
735 repo.ui.status(_("skipping missing subrepository: %s\n")
743 repo.ui.status(_("skipping missing subrepository: %s\n")
736 % join(subpath))
744 % join(subpath))
737
745
738 rejected = []
746 rejected = []
739 origbad = m.bad
747 origbad = m.bad
740 def badfn(f, msg):
748 def badfn(f, msg):
741 if f in m.files():
749 if f in m.files():
742 origbad(f, msg)
750 origbad(f, msg)
743 rejected.append(f)
751 rejected.append(f)
744
752
745 m.bad = badfn
753 m.bad = badfn
746 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
754 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
747 m.bad = origbad
755 m.bad = origbad
748
756
749 unknownset = set(unknown + forgotten)
757 unknownset = set(unknown + forgotten)
750 toprint = unknownset.copy()
758 toprint = unknownset.copy()
751 toprint.update(deleted)
759 toprint.update(deleted)
752 for abs in sorted(toprint):
760 for abs in sorted(toprint):
753 if repo.ui.verbose or not m.exact(abs):
761 if repo.ui.verbose or not m.exact(abs):
754 if abs in unknownset:
762 if abs in unknownset:
755 status = _('adding %s\n') % m.uipath(join(abs))
763 status = _('adding %s\n') % m.uipath(join(abs))
756 else:
764 else:
757 status = _('removing %s\n') % m.uipath(join(abs))
765 status = _('removing %s\n') % m.uipath(join(abs))
758 repo.ui.status(status)
766 repo.ui.status(status)
759
767
760 renames = _findrenames(repo, m, added + unknown, removed + deleted,
768 renames = _findrenames(repo, m, added + unknown, removed + deleted,
761 similarity)
769 similarity)
762
770
763 if not dry_run:
771 if not dry_run:
764 _markchanges(repo, unknown + forgotten, deleted, renames)
772 _markchanges(repo, unknown + forgotten, deleted, renames)
765
773
766 for f in rejected:
774 for f in rejected:
767 if f in m.files():
775 if f in m.files():
768 return 1
776 return 1
769 return ret
777 return ret
770
778
771 def marktouched(repo, files, similarity=0.0):
779 def marktouched(repo, files, similarity=0.0):
772 '''Assert that files have somehow been operated upon. files are relative to
780 '''Assert that files have somehow been operated upon. files are relative to
773 the repo root.'''
781 the repo root.'''
774 m = matchfiles(repo, files)
782 m = matchfiles(repo, files)
775 rejected = []
783 rejected = []
776 m.bad = lambda x, y: rejected.append(x)
784 m.bad = lambda x, y: rejected.append(x)
777
785
778 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
786 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
779
787
780 if repo.ui.verbose:
788 if repo.ui.verbose:
781 unknownset = set(unknown + forgotten)
789 unknownset = set(unknown + forgotten)
782 toprint = unknownset.copy()
790 toprint = unknownset.copy()
783 toprint.update(deleted)
791 toprint.update(deleted)
784 for abs in sorted(toprint):
792 for abs in sorted(toprint):
785 if abs in unknownset:
793 if abs in unknownset:
786 status = _('adding %s\n') % abs
794 status = _('adding %s\n') % abs
787 else:
795 else:
788 status = _('removing %s\n') % abs
796 status = _('removing %s\n') % abs
789 repo.ui.status(status)
797 repo.ui.status(status)
790
798
791 renames = _findrenames(repo, m, added + unknown, removed + deleted,
799 renames = _findrenames(repo, m, added + unknown, removed + deleted,
792 similarity)
800 similarity)
793
801
794 _markchanges(repo, unknown + forgotten, deleted, renames)
802 _markchanges(repo, unknown + forgotten, deleted, renames)
795
803
796 for f in rejected:
804 for f in rejected:
797 if f in m.files():
805 if f in m.files():
798 return 1
806 return 1
799 return 0
807 return 0
800
808
801 def _interestingfiles(repo, matcher):
809 def _interestingfiles(repo, matcher):
802 '''Walk dirstate with matcher, looking for files that addremove would care
810 '''Walk dirstate with matcher, looking for files that addremove would care
803 about.
811 about.
804
812
805 This is different from dirstate.status because it doesn't care about
813 This is different from dirstate.status because it doesn't care about
806 whether files are modified or clean.'''
814 whether files are modified or clean.'''
807 added, unknown, deleted, removed, forgotten = [], [], [], [], []
815 added, unknown, deleted, removed, forgotten = [], [], [], [], []
808 audit_path = pathutil.pathauditor(repo.root)
816 audit_path = pathutil.pathauditor(repo.root)
809
817
810 ctx = repo[None]
818 ctx = repo[None]
811 dirstate = repo.dirstate
819 dirstate = repo.dirstate
812 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
820 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
813 full=False)
821 full=False)
814 for abs, st in walkresults.iteritems():
822 for abs, st in walkresults.iteritems():
815 dstate = dirstate[abs]
823 dstate = dirstate[abs]
816 if dstate == '?' and audit_path.check(abs):
824 if dstate == '?' and audit_path.check(abs):
817 unknown.append(abs)
825 unknown.append(abs)
818 elif dstate != 'r' and not st:
826 elif dstate != 'r' and not st:
819 deleted.append(abs)
827 deleted.append(abs)
820 elif dstate == 'r' and st:
828 elif dstate == 'r' and st:
821 forgotten.append(abs)
829 forgotten.append(abs)
822 # for finding renames
830 # for finding renames
823 elif dstate == 'r' and not st:
831 elif dstate == 'r' and not st:
824 removed.append(abs)
832 removed.append(abs)
825 elif dstate == 'a':
833 elif dstate == 'a':
826 added.append(abs)
834 added.append(abs)
827
835
828 return added, unknown, deleted, removed, forgotten
836 return added, unknown, deleted, removed, forgotten
829
837
830 def _findrenames(repo, matcher, added, removed, similarity):
838 def _findrenames(repo, matcher, added, removed, similarity):
831 '''Find renames from removed files to added ones.'''
839 '''Find renames from removed files to added ones.'''
832 renames = {}
840 renames = {}
833 if similarity > 0:
841 if similarity > 0:
834 for old, new, score in similar.findrenames(repo, added, removed,
842 for old, new, score in similar.findrenames(repo, added, removed,
835 similarity):
843 similarity):
836 if (repo.ui.verbose or not matcher.exact(old)
844 if (repo.ui.verbose or not matcher.exact(old)
837 or not matcher.exact(new)):
845 or not matcher.exact(new)):
838 repo.ui.status(_('recording removal of %s as rename to %s '
846 repo.ui.status(_('recording removal of %s as rename to %s '
839 '(%d%% similar)\n') %
847 '(%d%% similar)\n') %
840 (matcher.rel(old), matcher.rel(new),
848 (matcher.rel(old), matcher.rel(new),
841 score * 100))
849 score * 100))
842 renames[new] = old
850 renames[new] = old
843 return renames
851 return renames
844
852
845 def _markchanges(repo, unknown, deleted, renames):
853 def _markchanges(repo, unknown, deleted, renames):
846 '''Marks the files in unknown as added, the files in deleted as removed,
854 '''Marks the files in unknown as added, the files in deleted as removed,
847 and the files in renames as copied.'''
855 and the files in renames as copied.'''
848 wctx = repo[None]
856 wctx = repo[None]
849 wlock = repo.wlock()
857 wlock = repo.wlock()
850 try:
858 try:
851 wctx.forget(deleted)
859 wctx.forget(deleted)
852 wctx.add(unknown)
860 wctx.add(unknown)
853 for new, old in renames.iteritems():
861 for new, old in renames.iteritems():
854 wctx.copy(old, new)
862 wctx.copy(old, new)
855 finally:
863 finally:
856 wlock.release()
864 wlock.release()
857
865
858 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
866 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
859 """Update the dirstate to reflect the intent of copying src to dst. For
867 """Update the dirstate to reflect the intent of copying src to dst. For
860 different reasons it might not end with dst being marked as copied from src.
868 different reasons it might not end with dst being marked as copied from src.
861 """
869 """
862 origsrc = repo.dirstate.copied(src) or src
870 origsrc = repo.dirstate.copied(src) or src
863 if dst == origsrc: # copying back a copy?
871 if dst == origsrc: # copying back a copy?
864 if repo.dirstate[dst] not in 'mn' and not dryrun:
872 if repo.dirstate[dst] not in 'mn' and not dryrun:
865 repo.dirstate.normallookup(dst)
873 repo.dirstate.normallookup(dst)
866 else:
874 else:
867 if repo.dirstate[origsrc] == 'a' and origsrc == src:
875 if repo.dirstate[origsrc] == 'a' and origsrc == src:
868 if not ui.quiet:
876 if not ui.quiet:
869 ui.warn(_("%s has not been committed yet, so no copy "
877 ui.warn(_("%s has not been committed yet, so no copy "
870 "data will be stored for %s.\n")
878 "data will be stored for %s.\n")
871 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
879 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
872 if repo.dirstate[dst] in '?r' and not dryrun:
880 if repo.dirstate[dst] in '?r' and not dryrun:
873 wctx.add([dst])
881 wctx.add([dst])
874 elif not dryrun:
882 elif not dryrun:
875 wctx.copy(origsrc, dst)
883 wctx.copy(origsrc, dst)
876
884
877 def readrequires(opener, supported):
885 def readrequires(opener, supported):
878 '''Reads and parses .hg/requires and checks if all entries found
886 '''Reads and parses .hg/requires and checks if all entries found
879 are in the list of supported features.'''
887 are in the list of supported features.'''
880 requirements = set(opener.read("requires").splitlines())
888 requirements = set(opener.read("requires").splitlines())
881 missings = []
889 missings = []
882 for r in requirements:
890 for r in requirements:
883 if r not in supported:
891 if r not in supported:
884 if not r or not r[0].isalnum():
892 if not r or not r[0].isalnum():
885 raise error.RequirementError(_(".hg/requires file is corrupt"))
893 raise error.RequirementError(_(".hg/requires file is corrupt"))
886 missings.append(r)
894 missings.append(r)
887 missings.sort()
895 missings.sort()
888 if missings:
896 if missings:
889 raise error.RequirementError(
897 raise error.RequirementError(
890 _("repository requires features unknown to this Mercurial: %s")
898 _("repository requires features unknown to this Mercurial: %s")
891 % " ".join(missings),
899 % " ".join(missings),
892 hint=_("see http://mercurial.selenic.com/wiki/MissingRequirement"
900 hint=_("see http://mercurial.selenic.com/wiki/MissingRequirement"
893 " for more information"))
901 " for more information"))
894 return requirements
902 return requirements
895
903
896 class filecachesubentry(object):
904 class filecachesubentry(object):
897 def __init__(self, path, stat):
905 def __init__(self, path, stat):
898 self.path = path
906 self.path = path
899 self.cachestat = None
907 self.cachestat = None
900 self._cacheable = None
908 self._cacheable = None
901
909
902 if stat:
910 if stat:
903 self.cachestat = filecachesubentry.stat(self.path)
911 self.cachestat = filecachesubentry.stat(self.path)
904
912
905 if self.cachestat:
913 if self.cachestat:
906 self._cacheable = self.cachestat.cacheable()
914 self._cacheable = self.cachestat.cacheable()
907 else:
915 else:
908 # None means we don't know yet
916 # None means we don't know yet
909 self._cacheable = None
917 self._cacheable = None
910
918
911 def refresh(self):
919 def refresh(self):
912 if self.cacheable():
920 if self.cacheable():
913 self.cachestat = filecachesubentry.stat(self.path)
921 self.cachestat = filecachesubentry.stat(self.path)
914
922
915 def cacheable(self):
923 def cacheable(self):
916 if self._cacheable is not None:
924 if self._cacheable is not None:
917 return self._cacheable
925 return self._cacheable
918
926
919 # we don't know yet, assume it is for now
927 # we don't know yet, assume it is for now
920 return True
928 return True
921
929
922 def changed(self):
930 def changed(self):
923 # no point in going further if we can't cache it
931 # no point in going further if we can't cache it
924 if not self.cacheable():
932 if not self.cacheable():
925 return True
933 return True
926
934
927 newstat = filecachesubentry.stat(self.path)
935 newstat = filecachesubentry.stat(self.path)
928
936
929 # we may not know if it's cacheable yet, check again now
937 # we may not know if it's cacheable yet, check again now
930 if newstat and self._cacheable is None:
938 if newstat and self._cacheable is None:
931 self._cacheable = newstat.cacheable()
939 self._cacheable = newstat.cacheable()
932
940
933 # check again
941 # check again
934 if not self._cacheable:
942 if not self._cacheable:
935 return True
943 return True
936
944
937 if self.cachestat != newstat:
945 if self.cachestat != newstat:
938 self.cachestat = newstat
946 self.cachestat = newstat
939 return True
947 return True
940 else:
948 else:
941 return False
949 return False
942
950
943 @staticmethod
951 @staticmethod
944 def stat(path):
952 def stat(path):
945 try:
953 try:
946 return util.cachestat(path)
954 return util.cachestat(path)
947 except OSError, e:
955 except OSError, e:
948 if e.errno != errno.ENOENT:
956 if e.errno != errno.ENOENT:
949 raise
957 raise
950
958
951 class filecacheentry(object):
959 class filecacheentry(object):
952 def __init__(self, paths, stat=True):
960 def __init__(self, paths, stat=True):
953 self._entries = []
961 self._entries = []
954 for path in paths:
962 for path in paths:
955 self._entries.append(filecachesubentry(path, stat))
963 self._entries.append(filecachesubentry(path, stat))
956
964
957 def changed(self):
965 def changed(self):
958 '''true if any entry has changed'''
966 '''true if any entry has changed'''
959 for entry in self._entries:
967 for entry in self._entries:
960 if entry.changed():
968 if entry.changed():
961 return True
969 return True
962 return False
970 return False
963
971
964 def refresh(self):
972 def refresh(self):
965 for entry in self._entries:
973 for entry in self._entries:
966 entry.refresh()
974 entry.refresh()
967
975
968 class filecache(object):
976 class filecache(object):
969 '''A property like decorator that tracks files under .hg/ for updates.
977 '''A property like decorator that tracks files under .hg/ for updates.
970
978
971 Records stat info when called in _filecache.
979 Records stat info when called in _filecache.
972
980
973 On subsequent calls, compares old stat info with new info, and recreates the
981 On subsequent calls, compares old stat info with new info, and recreates the
974 object when any of the files changes, updating the new stat info in
982 object when any of the files changes, updating the new stat info in
975 _filecache.
983 _filecache.
976
984
977 Mercurial either atomic renames or appends for files under .hg,
985 Mercurial either atomic renames or appends for files under .hg,
978 so to ensure the cache is reliable we need the filesystem to be able
986 so to ensure the cache is reliable we need the filesystem to be able
979 to tell us if a file has been replaced. If it can't, we fallback to
987 to tell us if a file has been replaced. If it can't, we fallback to
980 recreating the object on every call (essentially the same behaviour as
988 recreating the object on every call (essentially the same behaviour as
981 propertycache).
989 propertycache).
982
990
983 '''
991 '''
984 def __init__(self, *paths):
992 def __init__(self, *paths):
985 self.paths = paths
993 self.paths = paths
986
994
987 def join(self, obj, fname):
995 def join(self, obj, fname):
988 """Used to compute the runtime path of a cached file.
996 """Used to compute the runtime path of a cached file.
989
997
990 Users should subclass filecache and provide their own version of this
998 Users should subclass filecache and provide their own version of this
991 function to call the appropriate join function on 'obj' (an instance
999 function to call the appropriate join function on 'obj' (an instance
992 of the class that its member function was decorated).
1000 of the class that its member function was decorated).
993 """
1001 """
994 return obj.join(fname)
1002 return obj.join(fname)
995
1003
996 def __call__(self, func):
1004 def __call__(self, func):
997 self.func = func
1005 self.func = func
998 self.name = func.__name__
1006 self.name = func.__name__
999 return self
1007 return self
1000
1008
1001 def __get__(self, obj, type=None):
1009 def __get__(self, obj, type=None):
1002 # do we need to check if the file changed?
1010 # do we need to check if the file changed?
1003 if self.name in obj.__dict__:
1011 if self.name in obj.__dict__:
1004 assert self.name in obj._filecache, self.name
1012 assert self.name in obj._filecache, self.name
1005 return obj.__dict__[self.name]
1013 return obj.__dict__[self.name]
1006
1014
1007 entry = obj._filecache.get(self.name)
1015 entry = obj._filecache.get(self.name)
1008
1016
1009 if entry:
1017 if entry:
1010 if entry.changed():
1018 if entry.changed():
1011 entry.obj = self.func(obj)
1019 entry.obj = self.func(obj)
1012 else:
1020 else:
1013 paths = [self.join(obj, path) for path in self.paths]
1021 paths = [self.join(obj, path) for path in self.paths]
1014
1022
1015 # We stat -before- creating the object so our cache doesn't lie if
1023 # We stat -before- creating the object so our cache doesn't lie if
1016 # a writer modified between the time we read and stat
1024 # a writer modified between the time we read and stat
1017 entry = filecacheentry(paths, True)
1025 entry = filecacheentry(paths, True)
1018 entry.obj = self.func(obj)
1026 entry.obj = self.func(obj)
1019
1027
1020 obj._filecache[self.name] = entry
1028 obj._filecache[self.name] = entry
1021
1029
1022 obj.__dict__[self.name] = entry.obj
1030 obj.__dict__[self.name] = entry.obj
1023 return entry.obj
1031 return entry.obj
1024
1032
1025 def __set__(self, obj, value):
1033 def __set__(self, obj, value):
1026 if self.name not in obj._filecache:
1034 if self.name not in obj._filecache:
1027 # we add an entry for the missing value because X in __dict__
1035 # we add an entry for the missing value because X in __dict__
1028 # implies X in _filecache
1036 # implies X in _filecache
1029 paths = [self.join(obj, path) for path in self.paths]
1037 paths = [self.join(obj, path) for path in self.paths]
1030 ce = filecacheentry(paths, False)
1038 ce = filecacheentry(paths, False)
1031 obj._filecache[self.name] = ce
1039 obj._filecache[self.name] = ce
1032 else:
1040 else:
1033 ce = obj._filecache[self.name]
1041 ce = obj._filecache[self.name]
1034
1042
1035 ce.obj = value # update cached copy
1043 ce.obj = value # update cached copy
1036 obj.__dict__[self.name] = value # update copy returned by obj.x
1044 obj.__dict__[self.name] = value # update copy returned by obj.x
1037
1045
1038 def __delete__(self, obj):
1046 def __delete__(self, obj):
1039 try:
1047 try:
1040 del obj.__dict__[self.name]
1048 del obj.__dict__[self.name]
1041 except KeyError:
1049 except KeyError:
1042 raise AttributeError(self.name)
1050 raise AttributeError(self.name)
1043
1051
1044 class dirs(object):
1052 class dirs(object):
1045 '''a multiset of directory names from a dirstate or manifest'''
1053 '''a multiset of directory names from a dirstate or manifest'''
1046
1054
1047 def __init__(self, map, skip=None):
1055 def __init__(self, map, skip=None):
1048 self._dirs = {}
1056 self._dirs = {}
1049 addpath = self.addpath
1057 addpath = self.addpath
1050 if util.safehasattr(map, 'iteritems') and skip is not None:
1058 if util.safehasattr(map, 'iteritems') and skip is not None:
1051 for f, s in map.iteritems():
1059 for f, s in map.iteritems():
1052 if s[0] != skip:
1060 if s[0] != skip:
1053 addpath(f)
1061 addpath(f)
1054 else:
1062 else:
1055 for f in map:
1063 for f in map:
1056 addpath(f)
1064 addpath(f)
1057
1065
1058 def addpath(self, path):
1066 def addpath(self, path):
1059 dirs = self._dirs
1067 dirs = self._dirs
1060 for base in finddirs(path):
1068 for base in finddirs(path):
1061 if base in dirs:
1069 if base in dirs:
1062 dirs[base] += 1
1070 dirs[base] += 1
1063 return
1071 return
1064 dirs[base] = 1
1072 dirs[base] = 1
1065
1073
1066 def delpath(self, path):
1074 def delpath(self, path):
1067 dirs = self._dirs
1075 dirs = self._dirs
1068 for base in finddirs(path):
1076 for base in finddirs(path):
1069 if dirs[base] > 1:
1077 if dirs[base] > 1:
1070 dirs[base] -= 1
1078 dirs[base] -= 1
1071 return
1079 return
1072 del dirs[base]
1080 del dirs[base]
1073
1081
1074 def __iter__(self):
1082 def __iter__(self):
1075 return self._dirs.iterkeys()
1083 return self._dirs.iterkeys()
1076
1084
1077 def __contains__(self, d):
1085 def __contains__(self, d):
1078 return d in self._dirs
1086 return d in self._dirs
1079
1087
1080 if util.safehasattr(parsers, 'dirs'):
1088 if util.safehasattr(parsers, 'dirs'):
1081 dirs = parsers.dirs
1089 dirs = parsers.dirs
1082
1090
1083 def finddirs(path):
1091 def finddirs(path):
1084 pos = path.rfind('/')
1092 pos = path.rfind('/')
1085 while pos != -1:
1093 while pos != -1:
1086 yield path[:pos]
1094 yield path[:pos]
1087 pos = path.rfind('/', 0, pos)
1095 pos = path.rfind('/', 0, pos)
@@ -1,313 +1,322 b''
1 Preparing the subrepository 'sub2'
1 Preparing the subrepository 'sub2'
2
2
3 $ hg init sub2
3 $ hg init sub2
4 $ echo sub2 > sub2/sub2
4 $ echo sub2 > sub2/sub2
5 $ hg add -R sub2
5 $ hg add -R sub2
6 adding sub2/sub2 (glob)
6 adding sub2/sub2 (glob)
7 $ hg commit -R sub2 -m "sub2 import"
7 $ hg commit -R sub2 -m "sub2 import"
8
8
9 Preparing the 'sub1' repo which depends on the subrepo 'sub2'
9 Preparing the 'sub1' repo which depends on the subrepo 'sub2'
10
10
11 $ hg init sub1
11 $ hg init sub1
12 $ echo sub1 > sub1/sub1
12 $ echo sub1 > sub1/sub1
13 $ echo "sub2 = ../sub2" > sub1/.hgsub
13 $ echo "sub2 = ../sub2" > sub1/.hgsub
14 $ hg clone sub2 sub1/sub2
14 $ hg clone sub2 sub1/sub2
15 updating to branch default
15 updating to branch default
16 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
16 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
17 $ hg add -R sub1
17 $ hg add -R sub1
18 adding sub1/.hgsub (glob)
18 adding sub1/.hgsub (glob)
19 adding sub1/sub1 (glob)
19 adding sub1/sub1 (glob)
20 $ hg commit -R sub1 -m "sub1 import"
20 $ hg commit -R sub1 -m "sub1 import"
21
21
22 Preparing the 'main' repo which depends on the subrepo 'sub1'
22 Preparing the 'main' repo which depends on the subrepo 'sub1'
23
23
24 $ hg init main
24 $ hg init main
25 $ echo main > main/main
25 $ echo main > main/main
26 $ echo "sub1 = ../sub1" > main/.hgsub
26 $ echo "sub1 = ../sub1" > main/.hgsub
27 $ hg clone sub1 main/sub1
27 $ hg clone sub1 main/sub1
28 updating to branch default
28 updating to branch default
29 cloning subrepo sub2 from $TESTTMP/sub2
29 cloning subrepo sub2 from $TESTTMP/sub2
30 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
31 $ hg add -R main
31 $ hg add -R main
32 adding main/.hgsub (glob)
32 adding main/.hgsub (glob)
33 adding main/main (glob)
33 adding main/main (glob)
34 $ hg commit -R main -m "main import"
34 $ hg commit -R main -m "main import"
35
35
36 Cleaning both repositories, just as a clone -U
36 Cleaning both repositories, just as a clone -U
37
37
38 $ hg up -C -R sub2 null
38 $ hg up -C -R sub2 null
39 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
39 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
40 $ hg up -C -R sub1 null
40 $ hg up -C -R sub1 null
41 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
41 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
42 $ hg up -C -R main null
42 $ hg up -C -R main null
43 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
43 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
44 $ rm -rf main/sub1
44 $ rm -rf main/sub1
45 $ rm -rf sub1/sub2
45 $ rm -rf sub1/sub2
46
46
47 Clone main
47 Clone main
48
48
49 $ hg clone main cloned
49 $ hg clone main cloned
50 updating to branch default
50 updating to branch default
51 cloning subrepo sub1 from $TESTTMP/sub1
51 cloning subrepo sub1 from $TESTTMP/sub1
52 cloning subrepo sub1/sub2 from $TESTTMP/sub2 (glob)
52 cloning subrepo sub1/sub2 from $TESTTMP/sub2 (glob)
53 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
53 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
54
54
55 Checking cloned repo ids
55 Checking cloned repo ids
56
56
57 $ printf "cloned " ; hg id -R cloned
57 $ printf "cloned " ; hg id -R cloned
58 cloned 7f491f53a367 tip
58 cloned 7f491f53a367 tip
59 $ printf "cloned/sub1 " ; hg id -R cloned/sub1
59 $ printf "cloned/sub1 " ; hg id -R cloned/sub1
60 cloned/sub1 fc3b4ce2696f tip
60 cloned/sub1 fc3b4ce2696f tip
61 $ printf "cloned/sub1/sub2 " ; hg id -R cloned/sub1/sub2
61 $ printf "cloned/sub1/sub2 " ; hg id -R cloned/sub1/sub2
62 cloned/sub1/sub2 c57a0840e3ba tip
62 cloned/sub1/sub2 c57a0840e3ba tip
63
63
64 debugsub output for main and sub1
64 debugsub output for main and sub1
65
65
66 $ hg debugsub -R cloned
66 $ hg debugsub -R cloned
67 path sub1
67 path sub1
68 source ../sub1
68 source ../sub1
69 revision fc3b4ce2696f7741438c79207583768f2ce6b0dd
69 revision fc3b4ce2696f7741438c79207583768f2ce6b0dd
70 $ hg debugsub -R cloned/sub1
70 $ hg debugsub -R cloned/sub1
71 path sub2
71 path sub2
72 source ../sub2
72 source ../sub2
73 revision c57a0840e3badd667ef3c3ef65471609acb2ba3c
73 revision c57a0840e3badd667ef3c3ef65471609acb2ba3c
74
74
75 Modifying deeply nested 'sub2'
75 Modifying deeply nested 'sub2'
76
76
77 $ echo modified > cloned/sub1/sub2/sub2
77 $ echo modified > cloned/sub1/sub2/sub2
78 $ hg commit --subrepos -m "deep nested modif should trigger a commit" -R cloned
78 $ hg commit --subrepos -m "deep nested modif should trigger a commit" -R cloned
79 committing subrepository sub1
79 committing subrepository sub1
80 committing subrepository sub1/sub2 (glob)
80 committing subrepository sub1/sub2 (glob)
81
81
82 Checking modified node ids
82 Checking modified node ids
83
83
84 $ printf "cloned " ; hg id -R cloned
84 $ printf "cloned " ; hg id -R cloned
85 cloned ffe6649062fe tip
85 cloned ffe6649062fe tip
86 $ printf "cloned/sub1 " ; hg id -R cloned/sub1
86 $ printf "cloned/sub1 " ; hg id -R cloned/sub1
87 cloned/sub1 2ecb03bf44a9 tip
87 cloned/sub1 2ecb03bf44a9 tip
88 $ printf "cloned/sub1/sub2 " ; hg id -R cloned/sub1/sub2
88 $ printf "cloned/sub1/sub2 " ; hg id -R cloned/sub1/sub2
89 cloned/sub1/sub2 53dd3430bcaf tip
89 cloned/sub1/sub2 53dd3430bcaf tip
90
90
91 debugsub output for main and sub1
91 debugsub output for main and sub1
92
92
93 $ hg debugsub -R cloned
93 $ hg debugsub -R cloned
94 path sub1
94 path sub1
95 source ../sub1
95 source ../sub1
96 revision 2ecb03bf44a94e749e8669481dd9069526ce7cb9
96 revision 2ecb03bf44a94e749e8669481dd9069526ce7cb9
97 $ hg debugsub -R cloned/sub1
97 $ hg debugsub -R cloned/sub1
98 path sub2
98 path sub2
99 source ../sub2
99 source ../sub2
100 revision 53dd3430bcaf5ab4a7c48262bcad6d441f510487
100 revision 53dd3430bcaf5ab4a7c48262bcad6d441f510487
101
101
102 Check that deep archiving works
102 Check that deep archiving works
103
103
104 $ cd cloned
104 $ cd cloned
105 $ echo 'test' > sub1/sub2/test.txt
105 $ echo 'test' > sub1/sub2/test.txt
106 $ hg --config extensions.largefiles=! add sub1/sub2/test.txt
106 $ hg --config extensions.largefiles=! add sub1/sub2/test.txt
107 $ mkdir sub1/sub2/folder
107 $ mkdir sub1/sub2/folder
108 $ echo 'subfolder' > sub1/sub2/folder/test.txt
108 $ echo 'subfolder' > sub1/sub2/folder/test.txt
109 $ hg ci -ASm "add test.txt"
109 $ hg ci -ASm "add test.txt"
110 adding sub1/sub2/folder/test.txt (glob)
110 adding sub1/sub2/folder/test.txt (glob)
111 committing subrepository sub1
111 committing subrepository sub1
112 committing subrepository sub1/sub2 (glob)
112 committing subrepository sub1/sub2 (glob)
113
113
114 .. but first take a detour through some deep removal testing
114 .. but first take a detour through some deep removal testing
115
115
116 $ hg remove -S -I 're:.*.txt' .
116 $ hg remove -S -I 're:.*.txt' .
117 removing sub1/sub2/folder/test.txt (glob)
117 removing sub1/sub2/folder/test.txt (glob)
118 removing sub1/sub2/test.txt (glob)
118 removing sub1/sub2/test.txt (glob)
119 $ hg status -S
119 $ hg status -S
120 R sub1/sub2/folder/test.txt
120 R sub1/sub2/folder/test.txt
121 R sub1/sub2/test.txt
121 R sub1/sub2/test.txt
122 $ hg update -Cq
122 $ hg update -Cq
123 $ hg remove -I 're:.*.txt' sub1
123 $ hg remove -I 're:.*.txt' sub1
124 $ hg status -S
124 $ hg status -S
125 $ hg remove sub1/sub2/folder/test.txt
125 $ hg remove sub1/sub2/folder/test.txt
126 $ hg remove sub1/.hgsubstate
126 $ hg remove sub1/.hgsubstate
127 $ hg status -S
127 $ hg status -S
128 R sub1/.hgsubstate
128 R sub1/.hgsubstate
129 R sub1/sub2/folder/test.txt
129 R sub1/sub2/folder/test.txt
130 $ hg update -Cq
130 $ hg update -Cq
131 $ touch sub1/foo
131 $ touch sub1/foo
132 $ hg forget sub1/sub2/folder/test.txt
132 $ hg forget sub1/sub2/folder/test.txt
133 $ rm sub1/sub2/test.txt
133 $ rm sub1/sub2/test.txt
134
134
135 Test relative path printing + subrepos
135 Test relative path printing + subrepos
136 $ mkdir -p foo/bar
136 $ mkdir -p foo/bar
137 $ cd foo
137 $ cd foo
138 $ touch bar/abc
138 $ touch bar/abc
139 $ hg addremove -S ..
139 $ hg addremove -S ..
140 adding ../sub1/sub2/folder/test.txt (glob)
140 adding ../sub1/sub2/folder/test.txt (glob)
141 removing ../sub1/sub2/test.txt (glob)
141 removing ../sub1/sub2/test.txt (glob)
142 adding ../sub1/foo (glob)
142 adding ../sub1/foo (glob)
143 adding bar/abc (glob)
143 adding bar/abc (glob)
144 $ cd ..
144 $ cd ..
145 $ hg status -S
145 $ hg status -S
146 A foo/bar/abc
146 A foo/bar/abc
147 A sub1/foo
147 A sub1/foo
148 R sub1/sub2/test.txt
148 R sub1/sub2/test.txt
149 $ hg update -Cq
149 $ hg update -Cq
150 $ touch sub1/sub2/folder/bar
151 $ hg addremove sub1/sub2
152 adding sub1/sub2/folder/bar (glob)
153 $ hg status -S
154 A sub1/sub2/folder/bar
155 ? foo/bar/abc
156 ? sub1/foo
157 $ hg update -Cq
150 $ rm sub1/sub2/folder/test.txt
158 $ rm sub1/sub2/folder/test.txt
151 $ rm sub1/sub2/test.txt
159 $ rm sub1/sub2/test.txt
152 $ hg ci -ASm "remove test.txt"
160 $ hg ci -ASm "remove test.txt"
161 adding sub1/sub2/folder/bar (glob)
153 removing sub1/sub2/folder/test.txt (glob)
162 removing sub1/sub2/folder/test.txt (glob)
154 removing sub1/sub2/test.txt (glob)
163 removing sub1/sub2/test.txt (glob)
155 adding sub1/foo (glob)
164 adding sub1/foo (glob)
156 adding foo/bar/abc
165 adding foo/bar/abc
157 committing subrepository sub1
166 committing subrepository sub1
158 committing subrepository sub1/sub2 (glob)
167 committing subrepository sub1/sub2 (glob)
159 $ hg rollback -q
168 $ hg rollback -q
160 $ hg up -Cq
169 $ hg up -Cq
161
170
162 $ hg --config extensions.largefiles=! archive -S ../archive_all
171 $ hg --config extensions.largefiles=! archive -S ../archive_all
163 $ find ../archive_all | sort
172 $ find ../archive_all | sort
164 ../archive_all
173 ../archive_all
165 ../archive_all/.hg_archival.txt
174 ../archive_all/.hg_archival.txt
166 ../archive_all/.hgsub
175 ../archive_all/.hgsub
167 ../archive_all/.hgsubstate
176 ../archive_all/.hgsubstate
168 ../archive_all/main
177 ../archive_all/main
169 ../archive_all/sub1
178 ../archive_all/sub1
170 ../archive_all/sub1/.hgsub
179 ../archive_all/sub1/.hgsub
171 ../archive_all/sub1/.hgsubstate
180 ../archive_all/sub1/.hgsubstate
172 ../archive_all/sub1/sub1
181 ../archive_all/sub1/sub1
173 ../archive_all/sub1/sub2
182 ../archive_all/sub1/sub2
174 ../archive_all/sub1/sub2/folder
183 ../archive_all/sub1/sub2/folder
175 ../archive_all/sub1/sub2/folder/test.txt
184 ../archive_all/sub1/sub2/folder/test.txt
176 ../archive_all/sub1/sub2/sub2
185 ../archive_all/sub1/sub2/sub2
177 ../archive_all/sub1/sub2/test.txt
186 ../archive_all/sub1/sub2/test.txt
178
187
179 Check that archive -X works in deep subrepos
188 Check that archive -X works in deep subrepos
180
189
181 $ hg --config extensions.largefiles=! archive -S -X '**test*' ../archive_exclude
190 $ hg --config extensions.largefiles=! archive -S -X '**test*' ../archive_exclude
182 $ find ../archive_exclude | sort
191 $ find ../archive_exclude | sort
183 ../archive_exclude
192 ../archive_exclude
184 ../archive_exclude/.hg_archival.txt
193 ../archive_exclude/.hg_archival.txt
185 ../archive_exclude/.hgsub
194 ../archive_exclude/.hgsub
186 ../archive_exclude/.hgsubstate
195 ../archive_exclude/.hgsubstate
187 ../archive_exclude/main
196 ../archive_exclude/main
188 ../archive_exclude/sub1
197 ../archive_exclude/sub1
189 ../archive_exclude/sub1/.hgsub
198 ../archive_exclude/sub1/.hgsub
190 ../archive_exclude/sub1/.hgsubstate
199 ../archive_exclude/sub1/.hgsubstate
191 ../archive_exclude/sub1/sub1
200 ../archive_exclude/sub1/sub1
192 ../archive_exclude/sub1/sub2
201 ../archive_exclude/sub1/sub2
193 ../archive_exclude/sub1/sub2/sub2
202 ../archive_exclude/sub1/sub2/sub2
194
203
195 $ hg --config extensions.largefiles=! archive -S -I '**test*' ../archive_include
204 $ hg --config extensions.largefiles=! archive -S -I '**test*' ../archive_include
196 $ find ../archive_include | sort
205 $ find ../archive_include | sort
197 ../archive_include
206 ../archive_include
198 ../archive_include/sub1
207 ../archive_include/sub1
199 ../archive_include/sub1/sub2
208 ../archive_include/sub1/sub2
200 ../archive_include/sub1/sub2/folder
209 ../archive_include/sub1/sub2/folder
201 ../archive_include/sub1/sub2/folder/test.txt
210 ../archive_include/sub1/sub2/folder/test.txt
202 ../archive_include/sub1/sub2/test.txt
211 ../archive_include/sub1/sub2/test.txt
203
212
204 Check that deep archive works with largefiles (which overrides hgsubrepo impl)
213 Check that deep archive works with largefiles (which overrides hgsubrepo impl)
205 This also tests the repo.ui regression in 43fb170a23bd, and that lf subrepo
214 This also tests the repo.ui regression in 43fb170a23bd, and that lf subrepo
206 subrepos are archived properly.
215 subrepos are archived properly.
207 Note that add --large through a subrepo currently adds the file as a normal file
216 Note that add --large through a subrepo currently adds the file as a normal file
208
217
209 $ echo "large" > sub1/sub2/large.bin
218 $ echo "large" > sub1/sub2/large.bin
210 $ hg --config extensions.largefiles= add --large -R sub1/sub2 sub1/sub2/large.bin
219 $ hg --config extensions.largefiles= add --large -R sub1/sub2 sub1/sub2/large.bin
211 $ echo "large" > large.bin
220 $ echo "large" > large.bin
212 $ hg --config extensions.largefiles= add --large large.bin
221 $ hg --config extensions.largefiles= add --large large.bin
213 $ hg --config extensions.largefiles= ci -S -m "add large files"
222 $ hg --config extensions.largefiles= ci -S -m "add large files"
214 committing subrepository sub1
223 committing subrepository sub1
215 committing subrepository sub1/sub2 (glob)
224 committing subrepository sub1/sub2 (glob)
216
225
217 $ hg --config extensions.largefiles= archive -S ../archive_lf
226 $ hg --config extensions.largefiles= archive -S ../archive_lf
218 $ find ../archive_lf | sort
227 $ find ../archive_lf | sort
219 ../archive_lf
228 ../archive_lf
220 ../archive_lf/.hg_archival.txt
229 ../archive_lf/.hg_archival.txt
221 ../archive_lf/.hgsub
230 ../archive_lf/.hgsub
222 ../archive_lf/.hgsubstate
231 ../archive_lf/.hgsubstate
223 ../archive_lf/large.bin
232 ../archive_lf/large.bin
224 ../archive_lf/main
233 ../archive_lf/main
225 ../archive_lf/sub1
234 ../archive_lf/sub1
226 ../archive_lf/sub1/.hgsub
235 ../archive_lf/sub1/.hgsub
227 ../archive_lf/sub1/.hgsubstate
236 ../archive_lf/sub1/.hgsubstate
228 ../archive_lf/sub1/sub1
237 ../archive_lf/sub1/sub1
229 ../archive_lf/sub1/sub2
238 ../archive_lf/sub1/sub2
230 ../archive_lf/sub1/sub2/folder
239 ../archive_lf/sub1/sub2/folder
231 ../archive_lf/sub1/sub2/folder/test.txt
240 ../archive_lf/sub1/sub2/folder/test.txt
232 ../archive_lf/sub1/sub2/large.bin
241 ../archive_lf/sub1/sub2/large.bin
233 ../archive_lf/sub1/sub2/sub2
242 ../archive_lf/sub1/sub2/sub2
234 ../archive_lf/sub1/sub2/test.txt
243 ../archive_lf/sub1/sub2/test.txt
235 $ rm -rf ../archive_lf
244 $ rm -rf ../archive_lf
236
245
237 Exclude large files from main and sub-sub repo
246 Exclude large files from main and sub-sub repo
238
247
239 $ hg --config extensions.largefiles= archive -S -X '**.bin' ../archive_lf
248 $ hg --config extensions.largefiles= archive -S -X '**.bin' ../archive_lf
240 $ find ../archive_lf | sort
249 $ find ../archive_lf | sort
241 ../archive_lf
250 ../archive_lf
242 ../archive_lf/.hg_archival.txt
251 ../archive_lf/.hg_archival.txt
243 ../archive_lf/.hgsub
252 ../archive_lf/.hgsub
244 ../archive_lf/.hgsubstate
253 ../archive_lf/.hgsubstate
245 ../archive_lf/main
254 ../archive_lf/main
246 ../archive_lf/sub1
255 ../archive_lf/sub1
247 ../archive_lf/sub1/.hgsub
256 ../archive_lf/sub1/.hgsub
248 ../archive_lf/sub1/.hgsubstate
257 ../archive_lf/sub1/.hgsubstate
249 ../archive_lf/sub1/sub1
258 ../archive_lf/sub1/sub1
250 ../archive_lf/sub1/sub2
259 ../archive_lf/sub1/sub2
251 ../archive_lf/sub1/sub2/folder
260 ../archive_lf/sub1/sub2/folder
252 ../archive_lf/sub1/sub2/folder/test.txt
261 ../archive_lf/sub1/sub2/folder/test.txt
253 ../archive_lf/sub1/sub2/sub2
262 ../archive_lf/sub1/sub2/sub2
254 ../archive_lf/sub1/sub2/test.txt
263 ../archive_lf/sub1/sub2/test.txt
255 $ rm -rf ../archive_lf
264 $ rm -rf ../archive_lf
256
265
257 Exclude normal files from main and sub-sub repo
266 Exclude normal files from main and sub-sub repo
258
267
259 $ hg --config extensions.largefiles= archive -S -X '**.txt' ../archive_lf
268 $ hg --config extensions.largefiles= archive -S -X '**.txt' ../archive_lf
260 $ find ../archive_lf | sort
269 $ find ../archive_lf | sort
261 ../archive_lf
270 ../archive_lf
262 ../archive_lf/.hgsub
271 ../archive_lf/.hgsub
263 ../archive_lf/.hgsubstate
272 ../archive_lf/.hgsubstate
264 ../archive_lf/large.bin
273 ../archive_lf/large.bin
265 ../archive_lf/main
274 ../archive_lf/main
266 ../archive_lf/sub1
275 ../archive_lf/sub1
267 ../archive_lf/sub1/.hgsub
276 ../archive_lf/sub1/.hgsub
268 ../archive_lf/sub1/.hgsubstate
277 ../archive_lf/sub1/.hgsubstate
269 ../archive_lf/sub1/sub1
278 ../archive_lf/sub1/sub1
270 ../archive_lf/sub1/sub2
279 ../archive_lf/sub1/sub2
271 ../archive_lf/sub1/sub2/large.bin
280 ../archive_lf/sub1/sub2/large.bin
272 ../archive_lf/sub1/sub2/sub2
281 ../archive_lf/sub1/sub2/sub2
273 $ rm -rf ../archive_lf
282 $ rm -rf ../archive_lf
274
283
275 Include normal files from within a largefiles subrepo
284 Include normal files from within a largefiles subrepo
276
285
277 $ hg --config extensions.largefiles= archive -S -I '**.txt' ../archive_lf
286 $ hg --config extensions.largefiles= archive -S -I '**.txt' ../archive_lf
278 $ find ../archive_lf | sort
287 $ find ../archive_lf | sort
279 ../archive_lf
288 ../archive_lf
280 ../archive_lf/.hg_archival.txt
289 ../archive_lf/.hg_archival.txt
281 ../archive_lf/sub1
290 ../archive_lf/sub1
282 ../archive_lf/sub1/sub2
291 ../archive_lf/sub1/sub2
283 ../archive_lf/sub1/sub2/folder
292 ../archive_lf/sub1/sub2/folder
284 ../archive_lf/sub1/sub2/folder/test.txt
293 ../archive_lf/sub1/sub2/folder/test.txt
285 ../archive_lf/sub1/sub2/test.txt
294 ../archive_lf/sub1/sub2/test.txt
286 $ rm -rf ../archive_lf
295 $ rm -rf ../archive_lf
287
296
288 Include large files from within a largefiles subrepo
297 Include large files from within a largefiles subrepo
289
298
290 $ hg --config extensions.largefiles= archive -S -I '**.bin' ../archive_lf
299 $ hg --config extensions.largefiles= archive -S -I '**.bin' ../archive_lf
291 $ find ../archive_lf | sort
300 $ find ../archive_lf | sort
292 ../archive_lf
301 ../archive_lf
293 ../archive_lf/large.bin
302 ../archive_lf/large.bin
294 ../archive_lf/sub1
303 ../archive_lf/sub1
295 ../archive_lf/sub1/sub2
304 ../archive_lf/sub1/sub2
296 ../archive_lf/sub1/sub2/large.bin
305 ../archive_lf/sub1/sub2/large.bin
297 $ rm -rf ../archive_lf
306 $ rm -rf ../archive_lf
298
307
299 Find an exact largefile match in a largefiles subrepo
308 Find an exact largefile match in a largefiles subrepo
300
309
301 $ hg --config extensions.largefiles= archive -S -I 'sub1/sub2/large.bin' ../archive_lf
310 $ hg --config extensions.largefiles= archive -S -I 'sub1/sub2/large.bin' ../archive_lf
302 $ find ../archive_lf | sort
311 $ find ../archive_lf | sort
303 ../archive_lf
312 ../archive_lf
304 ../archive_lf/sub1
313 ../archive_lf/sub1
305 ../archive_lf/sub1/sub2
314 ../archive_lf/sub1/sub2
306 ../archive_lf/sub1/sub2/large.bin
315 ../archive_lf/sub1/sub2/large.bin
307 $ rm -rf ../archive_lf
316 $ rm -rf ../archive_lf
308
317
309 Find an exact match to a standin (should archive nothing)
318 Find an exact match to a standin (should archive nothing)
310 $ hg --config extensions.largefiles= archive -S -I 'sub/sub2/.hglf/large.bin' ../archive_lf
319 $ hg --config extensions.largefiles= archive -S -I 'sub/sub2/.hglf/large.bin' ../archive_lf
311 $ find ../archive_lf 2> /dev/null | sort
320 $ find ../archive_lf 2> /dev/null | sort
312
321
313 $ cd ..
322 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now