##// END OF EJS Templates
subrepos: support adding files in git subrepos...
Mathias De Maré -
r24174:bd9f64ec default
parent child Browse files
Show More
@@ -1,160 +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 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. However, if you specify the full
85 -S/--subrepos is specified. However, if you specify the full
86 path of a directory in a subrepo, addremove will be performed on
86 path of a directory in a subrepo, addremove will be performed on
87 it even without -S/--subrepos being specified. Git and
87 it even without -S/--subrepos being specified. Git and
88 Subversion subrepositories will print a warning and continue.
88 Subversion subrepositories will print a warning and continue.
89
89
90 :archive: archive does not recurse in subrepositories unless
90 :archive: archive does not recurse in subrepositories unless
91 -S/--subrepos is specified.
91 -S/--subrepos is specified.
92
92
93 :cat: cat currently only handles exact file matches in subrepos.
93 :cat: cat currently only handles exact file matches in subrepos.
94 Subversion subrepositories are currently ignored.
94 Subversion subrepositories are currently ignored.
95
95
96 :commit: commit creates a consistent snapshot of the state of the
96 :commit: commit creates a consistent snapshot of the state of the
97 entire project and its subrepositories. If any subrepositories
97 entire project and its subrepositories. If any subrepositories
98 have been modified, Mercurial will abort. Mercurial can be made
98 have been modified, Mercurial will abort. Mercurial can be made
99 to instead commit all modified subrepositories by specifying
99 to instead commit all modified subrepositories by specifying
100 -S/--subrepos, or setting "ui.commitsubrepos=True" in a
100 -S/--subrepos, or setting "ui.commitsubrepos=True" in a
101 configuration file (see :hg:`help config`). After there are no
101 configuration file (see :hg:`help config`). After there are no
102 longer any modified subrepositories, it records their state and
102 longer any modified subrepositories, it records their state and
103 finally commits it in the parent repository. The --addremove
103 finally commits it in the parent repository. The --addremove
104 option also honors the -S/--subrepos option. However, Git and
104 option also honors the -S/--subrepos option. However, Git and
105 Subversion subrepositories will print a warning and abort.
105 Subversion subrepositories will print a warning and abort.
106
106
107 :diff: diff does not recurse in subrepos unless -S/--subrepos is
107 :diff: diff does not recurse in subrepos unless -S/--subrepos is
108 specified. Changes are displayed as usual, on the subrepositories
108 specified. Changes are displayed as usual, on the subrepositories
109 elements. Git subrepositories do not support --include/--exclude.
109 elements. Git subrepositories do not support --include/--exclude.
110 Subversion subrepositories are currently silently ignored.
110 Subversion subrepositories are currently silently ignored.
111
111
112 :forget: forget currently only handles exact file matches in subrepos.
112 :forget: forget currently only handles exact file matches in subrepos.
113 Git and Subversion subrepositories are currently silently ignored.
113 Git and Subversion subrepositories are currently silently ignored.
114
114
115 :incoming: incoming does not recurse in subrepos unless -S/--subrepos
115 :incoming: incoming does not recurse in subrepos unless -S/--subrepos
116 is specified. Git and Subversion subrepositories are currently
116 is specified. Git and Subversion subrepositories are currently
117 silently ignored.
117 silently ignored.
118
118
119 :outgoing: outgoing does not recurse in subrepos unless -S/--subrepos
119 :outgoing: outgoing does not recurse in subrepos unless -S/--subrepos
120 is specified. Git and Subversion subrepositories are currently
120 is specified. Git and Subversion subrepositories are currently
121 silently ignored.
121 silently ignored.
122
122
123 :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
124 to running :hg:`update`. Listing and retrieving all
124 to running :hg:`update`. Listing and retrieving all
125 subrepositories changes referenced by the parent repository pulled
125 subrepositories changes referenced by the parent repository pulled
126 changesets is expensive at best, impossible in the Subversion
126 changesets is expensive at best, impossible in the Subversion
127 case.
127 case.
128
128
129 :push: Mercurial will automatically push all subrepositories first
129 :push: Mercurial will automatically push all subrepositories first
130 when the parent repository is being pushed. This ensures new
130 when the parent repository is being pushed. This ensures new
131 subrepository changes are available when referenced by top-level
131 subrepository changes are available when referenced by top-level
132 repositories. Push is a no-op for Subversion subrepositories.
132 repositories. Push is a no-op for Subversion subrepositories.
133
133
134 :status: status does not recurse into subrepositories unless
134 :status: status does not recurse into subrepositories unless
135 -S/--subrepos is specified. Subrepository changes are displayed as
135 -S/--subrepos is specified. Subrepository changes are displayed as
136 regular Mercurial changes on the subrepository
136 regular Mercurial changes on the subrepository
137 elements. Subversion subrepositories are currently silently
137 elements. Subversion subrepositories are currently silently
138 ignored.
138 ignored.
139
139
140 :remove: remove does not recurse into subrepositories unless
140 :remove: remove does not recurse into subrepositories unless
141 -S/--subrepos is specified. However, if you specify a file or
141 -S/--subrepos is specified. However, if you specify a file or
142 directory path in a subrepo, it will be removed even without
142 directory path in a subrepo, it will be removed even without
143 -S/--subrepos. Git and Subversion subrepositories are currently
143 -S/--subrepos. Git and Subversion subrepositories are currently
144 silently ignored.
144 silently ignored.
145
145
146 :update: update restores the subrepos in the state they were
146 :update: update restores the subrepos in the state they were
147 originally committed in target changeset. If the recorded
147 originally committed in target changeset. If the recorded
148 changeset is not available in the current subrepository, Mercurial
148 changeset is not available in the current subrepository, Mercurial
149 will pull it in first before updating. This means that updating
149 will pull it in first before updating. This means that updating
150 can require network access when using subrepositories.
150 can require network access when using subrepositories.
151
151
152 Remapping Subrepositories Sources
152 Remapping Subrepositories Sources
153 =================================
153 =================================
154
154
155 A subrepository source location may change during a project life,
155 A subrepository source location may change during a project life,
156 invalidating references stored in the parent repository history. To
156 invalidating references stored in the parent repository history. To
157 fix this, rewriting rules can be defined in parent repository ``hgrc``
157 fix this, rewriting rules can be defined in parent repository ``hgrc``
158 file or in Mercurial configuration. See the ``[subpaths]`` section in
158 file or in Mercurial configuration. See the ``[subpaths]`` section in
159 hgrc(5) for more details.
159 hgrc(5) for more details.
160
160
@@ -1,1705 +1,1728 b''
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository handling for Mercurial
2 #
2 #
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-2010 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 import copy
8 import copy
9 import errno, os, re, shutil, posixpath, sys
9 import errno, os, re, shutil, posixpath, sys
10 import xml.dom.minidom
10 import xml.dom.minidom
11 import stat, subprocess, tarfile
11 import stat, subprocess, tarfile
12 from i18n import _
12 from i18n import _
13 import config, util, node, error, cmdutil, scmutil, match as matchmod
13 import config, util, node, error, cmdutil, scmutil, match as matchmod
14 import phases
14 import phases
15 import pathutil
15 import pathutil
16 import exchange
16 import exchange
17 hg = None
17 hg = None
18 propertycache = util.propertycache
18 propertycache = util.propertycache
19
19
20 nullstate = ('', '', 'empty')
20 nullstate = ('', '', 'empty')
21
21
22 def _expandedabspath(path):
22 def _expandedabspath(path):
23 '''
23 '''
24 get a path or url and if it is a path expand it and return an absolute path
24 get a path or url and if it is a path expand it and return an absolute path
25 '''
25 '''
26 expandedpath = util.urllocalpath(util.expandpath(path))
26 expandedpath = util.urllocalpath(util.expandpath(path))
27 u = util.url(expandedpath)
27 u = util.url(expandedpath)
28 if not u.scheme:
28 if not u.scheme:
29 path = util.normpath(os.path.abspath(u.path))
29 path = util.normpath(os.path.abspath(u.path))
30 return path
30 return path
31
31
32 def _getstorehashcachename(remotepath):
32 def _getstorehashcachename(remotepath):
33 '''get a unique filename for the store hash cache of a remote repository'''
33 '''get a unique filename for the store hash cache of a remote repository'''
34 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
34 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
35
35
36 class SubrepoAbort(error.Abort):
36 class SubrepoAbort(error.Abort):
37 """Exception class used to avoid handling a subrepo error more than once"""
37 """Exception class used to avoid handling a subrepo error more than once"""
38 def __init__(self, *args, **kw):
38 def __init__(self, *args, **kw):
39 error.Abort.__init__(self, *args, **kw)
39 error.Abort.__init__(self, *args, **kw)
40 self.subrepo = kw.get('subrepo')
40 self.subrepo = kw.get('subrepo')
41 self.cause = kw.get('cause')
41 self.cause = kw.get('cause')
42
42
43 def annotatesubrepoerror(func):
43 def annotatesubrepoerror(func):
44 def decoratedmethod(self, *args, **kargs):
44 def decoratedmethod(self, *args, **kargs):
45 try:
45 try:
46 res = func(self, *args, **kargs)
46 res = func(self, *args, **kargs)
47 except SubrepoAbort, ex:
47 except SubrepoAbort, ex:
48 # This exception has already been handled
48 # This exception has already been handled
49 raise ex
49 raise ex
50 except error.Abort, ex:
50 except error.Abort, ex:
51 subrepo = subrelpath(self)
51 subrepo = subrelpath(self)
52 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
52 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
53 # avoid handling this exception by raising a SubrepoAbort exception
53 # avoid handling this exception by raising a SubrepoAbort exception
54 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
54 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
55 cause=sys.exc_info())
55 cause=sys.exc_info())
56 return res
56 return res
57 return decoratedmethod
57 return decoratedmethod
58
58
59 def state(ctx, ui):
59 def state(ctx, ui):
60 """return a state dict, mapping subrepo paths configured in .hgsub
60 """return a state dict, mapping subrepo paths configured in .hgsub
61 to tuple: (source from .hgsub, revision from .hgsubstate, kind
61 to tuple: (source from .hgsub, revision from .hgsubstate, kind
62 (key in types dict))
62 (key in types dict))
63 """
63 """
64 p = config.config()
64 p = config.config()
65 def read(f, sections=None, remap=None):
65 def read(f, sections=None, remap=None):
66 if f in ctx:
66 if f in ctx:
67 try:
67 try:
68 data = ctx[f].data()
68 data = ctx[f].data()
69 except IOError, err:
69 except IOError, err:
70 if err.errno != errno.ENOENT:
70 if err.errno != errno.ENOENT:
71 raise
71 raise
72 # handle missing subrepo spec files as removed
72 # handle missing subrepo spec files as removed
73 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
73 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
74 return
74 return
75 p.parse(f, data, sections, remap, read)
75 p.parse(f, data, sections, remap, read)
76 else:
76 else:
77 raise util.Abort(_("subrepo spec file %s not found") % f)
77 raise util.Abort(_("subrepo spec file %s not found") % f)
78
78
79 if '.hgsub' in ctx:
79 if '.hgsub' in ctx:
80 read('.hgsub')
80 read('.hgsub')
81
81
82 for path, src in ui.configitems('subpaths'):
82 for path, src in ui.configitems('subpaths'):
83 p.set('subpaths', path, src, ui.configsource('subpaths', path))
83 p.set('subpaths', path, src, ui.configsource('subpaths', path))
84
84
85 rev = {}
85 rev = {}
86 if '.hgsubstate' in ctx:
86 if '.hgsubstate' in ctx:
87 try:
87 try:
88 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
88 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
89 l = l.lstrip()
89 l = l.lstrip()
90 if not l:
90 if not l:
91 continue
91 continue
92 try:
92 try:
93 revision, path = l.split(" ", 1)
93 revision, path = l.split(" ", 1)
94 except ValueError:
94 except ValueError:
95 raise util.Abort(_("invalid subrepository revision "
95 raise util.Abort(_("invalid subrepository revision "
96 "specifier in .hgsubstate line %d")
96 "specifier in .hgsubstate line %d")
97 % (i + 1))
97 % (i + 1))
98 rev[path] = revision
98 rev[path] = revision
99 except IOError, err:
99 except IOError, err:
100 if err.errno != errno.ENOENT:
100 if err.errno != errno.ENOENT:
101 raise
101 raise
102
102
103 def remap(src):
103 def remap(src):
104 for pattern, repl in p.items('subpaths'):
104 for pattern, repl in p.items('subpaths'):
105 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
105 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
106 # does a string decode.
106 # does a string decode.
107 repl = repl.encode('string-escape')
107 repl = repl.encode('string-escape')
108 # However, we still want to allow back references to go
108 # However, we still want to allow back references to go
109 # through unharmed, so we turn r'\\1' into r'\1'. Again,
109 # through unharmed, so we turn r'\\1' into r'\1'. Again,
110 # extra escapes are needed because re.sub string decodes.
110 # extra escapes are needed because re.sub string decodes.
111 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
111 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
112 try:
112 try:
113 src = re.sub(pattern, repl, src, 1)
113 src = re.sub(pattern, repl, src, 1)
114 except re.error, e:
114 except re.error, e:
115 raise util.Abort(_("bad subrepository pattern in %s: %s")
115 raise util.Abort(_("bad subrepository pattern in %s: %s")
116 % (p.source('subpaths', pattern), e))
116 % (p.source('subpaths', pattern), e))
117 return src
117 return src
118
118
119 state = {}
119 state = {}
120 for path, src in p[''].items():
120 for path, src in p[''].items():
121 kind = 'hg'
121 kind = 'hg'
122 if src.startswith('['):
122 if src.startswith('['):
123 if ']' not in src:
123 if ']' not in src:
124 raise util.Abort(_('missing ] in subrepo source'))
124 raise util.Abort(_('missing ] in subrepo source'))
125 kind, src = src.split(']', 1)
125 kind, src = src.split(']', 1)
126 kind = kind[1:]
126 kind = kind[1:]
127 src = src.lstrip() # strip any extra whitespace after ']'
127 src = src.lstrip() # strip any extra whitespace after ']'
128
128
129 if not util.url(src).isabs():
129 if not util.url(src).isabs():
130 parent = _abssource(ctx._repo, abort=False)
130 parent = _abssource(ctx._repo, abort=False)
131 if parent:
131 if parent:
132 parent = util.url(parent)
132 parent = util.url(parent)
133 parent.path = posixpath.join(parent.path or '', src)
133 parent.path = posixpath.join(parent.path or '', src)
134 parent.path = posixpath.normpath(parent.path)
134 parent.path = posixpath.normpath(parent.path)
135 joined = str(parent)
135 joined = str(parent)
136 # Remap the full joined path and use it if it changes,
136 # Remap the full joined path and use it if it changes,
137 # else remap the original source.
137 # else remap the original source.
138 remapped = remap(joined)
138 remapped = remap(joined)
139 if remapped == joined:
139 if remapped == joined:
140 src = remap(src)
140 src = remap(src)
141 else:
141 else:
142 src = remapped
142 src = remapped
143
143
144 src = remap(src)
144 src = remap(src)
145 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
145 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
146
146
147 return state
147 return state
148
148
149 def writestate(repo, state):
149 def writestate(repo, state):
150 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
150 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
151 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
151 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
152 repo.wwrite('.hgsubstate', ''.join(lines), '')
152 repo.wwrite('.hgsubstate', ''.join(lines), '')
153
153
154 def submerge(repo, wctx, mctx, actx, overwrite):
154 def submerge(repo, wctx, mctx, actx, overwrite):
155 """delegated from merge.applyupdates: merging of .hgsubstate file
155 """delegated from merge.applyupdates: merging of .hgsubstate file
156 in working context, merging context and ancestor context"""
156 in working context, merging context and ancestor context"""
157 if mctx == actx: # backwards?
157 if mctx == actx: # backwards?
158 actx = wctx.p1()
158 actx = wctx.p1()
159 s1 = wctx.substate
159 s1 = wctx.substate
160 s2 = mctx.substate
160 s2 = mctx.substate
161 sa = actx.substate
161 sa = actx.substate
162 sm = {}
162 sm = {}
163
163
164 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
164 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
165
165
166 def debug(s, msg, r=""):
166 def debug(s, msg, r=""):
167 if r:
167 if r:
168 r = "%s:%s:%s" % r
168 r = "%s:%s:%s" % r
169 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
169 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
170
170
171 for s, l in sorted(s1.iteritems()):
171 for s, l in sorted(s1.iteritems()):
172 a = sa.get(s, nullstate)
172 a = sa.get(s, nullstate)
173 ld = l # local state with possible dirty flag for compares
173 ld = l # local state with possible dirty flag for compares
174 if wctx.sub(s).dirty():
174 if wctx.sub(s).dirty():
175 ld = (l[0], l[1] + "+")
175 ld = (l[0], l[1] + "+")
176 if wctx == actx: # overwrite
176 if wctx == actx: # overwrite
177 a = ld
177 a = ld
178
178
179 if s in s2:
179 if s in s2:
180 r = s2[s]
180 r = s2[s]
181 if ld == r or r == a: # no change or local is newer
181 if ld == r or r == a: # no change or local is newer
182 sm[s] = l
182 sm[s] = l
183 continue
183 continue
184 elif ld == a: # other side changed
184 elif ld == a: # other side changed
185 debug(s, "other changed, get", r)
185 debug(s, "other changed, get", r)
186 wctx.sub(s).get(r, overwrite)
186 wctx.sub(s).get(r, overwrite)
187 sm[s] = r
187 sm[s] = r
188 elif ld[0] != r[0]: # sources differ
188 elif ld[0] != r[0]: # sources differ
189 if repo.ui.promptchoice(
189 if repo.ui.promptchoice(
190 _(' subrepository sources for %s differ\n'
190 _(' subrepository sources for %s differ\n'
191 'use (l)ocal source (%s) or (r)emote source (%s)?'
191 'use (l)ocal source (%s) or (r)emote source (%s)?'
192 '$$ &Local $$ &Remote') % (s, l[0], r[0]), 0):
192 '$$ &Local $$ &Remote') % (s, l[0], r[0]), 0):
193 debug(s, "prompt changed, get", r)
193 debug(s, "prompt changed, get", r)
194 wctx.sub(s).get(r, overwrite)
194 wctx.sub(s).get(r, overwrite)
195 sm[s] = r
195 sm[s] = r
196 elif ld[1] == a[1]: # local side is unchanged
196 elif ld[1] == a[1]: # local side is unchanged
197 debug(s, "other side changed, get", r)
197 debug(s, "other side changed, get", r)
198 wctx.sub(s).get(r, overwrite)
198 wctx.sub(s).get(r, overwrite)
199 sm[s] = r
199 sm[s] = r
200 else:
200 else:
201 debug(s, "both sides changed")
201 debug(s, "both sides changed")
202 srepo = wctx.sub(s)
202 srepo = wctx.sub(s)
203 option = repo.ui.promptchoice(
203 option = repo.ui.promptchoice(
204 _(' subrepository %s diverged (local revision: %s, '
204 _(' subrepository %s diverged (local revision: %s, '
205 'remote revision: %s)\n'
205 'remote revision: %s)\n'
206 '(M)erge, keep (l)ocal or keep (r)emote?'
206 '(M)erge, keep (l)ocal or keep (r)emote?'
207 '$$ &Merge $$ &Local $$ &Remote')
207 '$$ &Merge $$ &Local $$ &Remote')
208 % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
208 % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
209 if option == 0:
209 if option == 0:
210 wctx.sub(s).merge(r)
210 wctx.sub(s).merge(r)
211 sm[s] = l
211 sm[s] = l
212 debug(s, "merge with", r)
212 debug(s, "merge with", r)
213 elif option == 1:
213 elif option == 1:
214 sm[s] = l
214 sm[s] = l
215 debug(s, "keep local subrepo revision", l)
215 debug(s, "keep local subrepo revision", l)
216 else:
216 else:
217 wctx.sub(s).get(r, overwrite)
217 wctx.sub(s).get(r, overwrite)
218 sm[s] = r
218 sm[s] = r
219 debug(s, "get remote subrepo revision", r)
219 debug(s, "get remote subrepo revision", r)
220 elif ld == a: # remote removed, local unchanged
220 elif ld == a: # remote removed, local unchanged
221 debug(s, "remote removed, remove")
221 debug(s, "remote removed, remove")
222 wctx.sub(s).remove()
222 wctx.sub(s).remove()
223 elif a == nullstate: # not present in remote or ancestor
223 elif a == nullstate: # not present in remote or ancestor
224 debug(s, "local added, keep")
224 debug(s, "local added, keep")
225 sm[s] = l
225 sm[s] = l
226 continue
226 continue
227 else:
227 else:
228 if repo.ui.promptchoice(
228 if repo.ui.promptchoice(
229 _(' local changed subrepository %s which remote removed\n'
229 _(' local changed subrepository %s which remote removed\n'
230 'use (c)hanged version or (d)elete?'
230 'use (c)hanged version or (d)elete?'
231 '$$ &Changed $$ &Delete') % s, 0):
231 '$$ &Changed $$ &Delete') % s, 0):
232 debug(s, "prompt remove")
232 debug(s, "prompt remove")
233 wctx.sub(s).remove()
233 wctx.sub(s).remove()
234
234
235 for s, r in sorted(s2.items()):
235 for s, r in sorted(s2.items()):
236 if s in s1:
236 if s in s1:
237 continue
237 continue
238 elif s not in sa:
238 elif s not in sa:
239 debug(s, "remote added, get", r)
239 debug(s, "remote added, get", r)
240 mctx.sub(s).get(r)
240 mctx.sub(s).get(r)
241 sm[s] = r
241 sm[s] = r
242 elif r != sa[s]:
242 elif r != sa[s]:
243 if repo.ui.promptchoice(
243 if repo.ui.promptchoice(
244 _(' remote changed subrepository %s which local removed\n'
244 _(' remote changed subrepository %s which local removed\n'
245 'use (c)hanged version or (d)elete?'
245 'use (c)hanged version or (d)elete?'
246 '$$ &Changed $$ &Delete') % s, 0) == 0:
246 '$$ &Changed $$ &Delete') % s, 0) == 0:
247 debug(s, "prompt recreate", r)
247 debug(s, "prompt recreate", r)
248 mctx.sub(s).get(r)
248 mctx.sub(s).get(r)
249 sm[s] = r
249 sm[s] = r
250
250
251 # record merged .hgsubstate
251 # record merged .hgsubstate
252 writestate(repo, sm)
252 writestate(repo, sm)
253 return sm
253 return sm
254
254
255 def _updateprompt(ui, sub, dirty, local, remote):
255 def _updateprompt(ui, sub, dirty, local, remote):
256 if dirty:
256 if dirty:
257 msg = (_(' subrepository sources for %s differ\n'
257 msg = (_(' subrepository sources for %s differ\n'
258 'use (l)ocal source (%s) or (r)emote source (%s)?'
258 'use (l)ocal source (%s) or (r)emote source (%s)?'
259 '$$ &Local $$ &Remote')
259 '$$ &Local $$ &Remote')
260 % (subrelpath(sub), local, remote))
260 % (subrelpath(sub), local, remote))
261 else:
261 else:
262 msg = (_(' subrepository sources for %s differ (in checked out '
262 msg = (_(' subrepository sources for %s differ (in checked out '
263 'version)\n'
263 'version)\n'
264 'use (l)ocal source (%s) or (r)emote source (%s)?'
264 'use (l)ocal source (%s) or (r)emote source (%s)?'
265 '$$ &Local $$ &Remote')
265 '$$ &Local $$ &Remote')
266 % (subrelpath(sub), local, remote))
266 % (subrelpath(sub), local, remote))
267 return ui.promptchoice(msg, 0)
267 return ui.promptchoice(msg, 0)
268
268
269 def reporelpath(repo):
269 def reporelpath(repo):
270 """return path to this (sub)repo as seen from outermost repo"""
270 """return path to this (sub)repo as seen from outermost repo"""
271 parent = repo
271 parent = repo
272 while util.safehasattr(parent, '_subparent'):
272 while util.safehasattr(parent, '_subparent'):
273 parent = parent._subparent
273 parent = parent._subparent
274 return repo.root[len(pathutil.normasprefix(parent.root)):]
274 return repo.root[len(pathutil.normasprefix(parent.root)):]
275
275
276 def subrelpath(sub):
276 def subrelpath(sub):
277 """return path to this subrepo as seen from outermost repo"""
277 """return path to this subrepo as seen from outermost repo"""
278 if util.safehasattr(sub, '_relpath'):
278 if util.safehasattr(sub, '_relpath'):
279 return sub._relpath
279 return sub._relpath
280 if not util.safehasattr(sub, '_repo'):
280 if not util.safehasattr(sub, '_repo'):
281 return sub._path
281 return sub._path
282 return reporelpath(sub._repo)
282 return reporelpath(sub._repo)
283
283
284 def _abssource(repo, push=False, abort=True):
284 def _abssource(repo, push=False, abort=True):
285 """return pull/push path of repo - either based on parent repo .hgsub info
285 """return pull/push path of repo - either based on parent repo .hgsub info
286 or on the top repo config. Abort or return None if no source found."""
286 or on the top repo config. Abort or return None if no source found."""
287 if util.safehasattr(repo, '_subparent'):
287 if util.safehasattr(repo, '_subparent'):
288 source = util.url(repo._subsource)
288 source = util.url(repo._subsource)
289 if source.isabs():
289 if source.isabs():
290 return str(source)
290 return str(source)
291 source.path = posixpath.normpath(source.path)
291 source.path = posixpath.normpath(source.path)
292 parent = _abssource(repo._subparent, push, abort=False)
292 parent = _abssource(repo._subparent, push, abort=False)
293 if parent:
293 if parent:
294 parent = util.url(util.pconvert(parent))
294 parent = util.url(util.pconvert(parent))
295 parent.path = posixpath.join(parent.path or '', source.path)
295 parent.path = posixpath.join(parent.path or '', source.path)
296 parent.path = posixpath.normpath(parent.path)
296 parent.path = posixpath.normpath(parent.path)
297 return str(parent)
297 return str(parent)
298 else: # recursion reached top repo
298 else: # recursion reached top repo
299 if util.safehasattr(repo, '_subtoppath'):
299 if util.safehasattr(repo, '_subtoppath'):
300 return repo._subtoppath
300 return repo._subtoppath
301 if push and repo.ui.config('paths', 'default-push'):
301 if push and repo.ui.config('paths', 'default-push'):
302 return repo.ui.config('paths', 'default-push')
302 return repo.ui.config('paths', 'default-push')
303 if repo.ui.config('paths', 'default'):
303 if repo.ui.config('paths', 'default'):
304 return repo.ui.config('paths', 'default')
304 return repo.ui.config('paths', 'default')
305 if repo.shared():
305 if repo.shared():
306 # chop off the .hg component to get the default path form
306 # chop off the .hg component to get the default path form
307 return os.path.dirname(repo.sharedpath)
307 return os.path.dirname(repo.sharedpath)
308 if abort:
308 if abort:
309 raise util.Abort(_("default path for subrepository not found"))
309 raise util.Abort(_("default path for subrepository not found"))
310
310
311 def _sanitize(ui, path, ignore):
311 def _sanitize(ui, path, ignore):
312 for dirname, dirs, names in os.walk(path):
312 for dirname, dirs, names in os.walk(path):
313 for i, d in enumerate(dirs):
313 for i, d in enumerate(dirs):
314 if d.lower() == ignore:
314 if d.lower() == ignore:
315 del dirs[i]
315 del dirs[i]
316 break
316 break
317 if os.path.basename(dirname).lower() != '.hg':
317 if os.path.basename(dirname).lower() != '.hg':
318 continue
318 continue
319 for f in names:
319 for f in names:
320 if f.lower() == 'hgrc':
320 if f.lower() == 'hgrc':
321 ui.warn(_("warning: removing potentially hostile 'hgrc' "
321 ui.warn(_("warning: removing potentially hostile 'hgrc' "
322 "in '%s'\n") % dirname)
322 "in '%s'\n") % dirname)
323 os.unlink(os.path.join(dirname, f))
323 os.unlink(os.path.join(dirname, f))
324
324
325 def subrepo(ctx, path):
325 def subrepo(ctx, path):
326 """return instance of the right subrepo class for subrepo in path"""
326 """return instance of the right subrepo class for subrepo in path"""
327 # subrepo inherently violates our import layering rules
327 # subrepo inherently violates our import layering rules
328 # because it wants to make repo objects from deep inside the stack
328 # because it wants to make repo objects from deep inside the stack
329 # so we manually delay the circular imports to not break
329 # so we manually delay the circular imports to not break
330 # scripts that don't use our demand-loading
330 # scripts that don't use our demand-loading
331 global hg
331 global hg
332 import hg as h
332 import hg as h
333 hg = h
333 hg = h
334
334
335 pathutil.pathauditor(ctx._repo.root)(path)
335 pathutil.pathauditor(ctx._repo.root)(path)
336 state = ctx.substate[path]
336 state = ctx.substate[path]
337 if state[2] not in types:
337 if state[2] not in types:
338 raise util.Abort(_('unknown subrepo type %s') % state[2])
338 raise util.Abort(_('unknown subrepo type %s') % state[2])
339 return types[state[2]](ctx, path, state[:2])
339 return types[state[2]](ctx, path, state[:2])
340
340
341 def newcommitphase(ui, ctx):
341 def newcommitphase(ui, ctx):
342 commitphase = phases.newcommitphase(ui)
342 commitphase = phases.newcommitphase(ui)
343 substate = getattr(ctx, "substate", None)
343 substate = getattr(ctx, "substate", None)
344 if not substate:
344 if not substate:
345 return commitphase
345 return commitphase
346 check = ui.config('phases', 'checksubrepos', 'follow')
346 check = ui.config('phases', 'checksubrepos', 'follow')
347 if check not in ('ignore', 'follow', 'abort'):
347 if check not in ('ignore', 'follow', 'abort'):
348 raise util.Abort(_('invalid phases.checksubrepos configuration: %s')
348 raise util.Abort(_('invalid phases.checksubrepos configuration: %s')
349 % (check))
349 % (check))
350 if check == 'ignore':
350 if check == 'ignore':
351 return commitphase
351 return commitphase
352 maxphase = phases.public
352 maxphase = phases.public
353 maxsub = None
353 maxsub = None
354 for s in sorted(substate):
354 for s in sorted(substate):
355 sub = ctx.sub(s)
355 sub = ctx.sub(s)
356 subphase = sub.phase(substate[s][1])
356 subphase = sub.phase(substate[s][1])
357 if maxphase < subphase:
357 if maxphase < subphase:
358 maxphase = subphase
358 maxphase = subphase
359 maxsub = s
359 maxsub = s
360 if commitphase < maxphase:
360 if commitphase < maxphase:
361 if check == 'abort':
361 if check == 'abort':
362 raise util.Abort(_("can't commit in %s phase"
362 raise util.Abort(_("can't commit in %s phase"
363 " conflicting %s from subrepository %s") %
363 " conflicting %s from subrepository %s") %
364 (phases.phasenames[commitphase],
364 (phases.phasenames[commitphase],
365 phases.phasenames[maxphase], maxsub))
365 phases.phasenames[maxphase], maxsub))
366 ui.warn(_("warning: changes are committed in"
366 ui.warn(_("warning: changes are committed in"
367 " %s phase from subrepository %s\n") %
367 " %s phase from subrepository %s\n") %
368 (phases.phasenames[maxphase], maxsub))
368 (phases.phasenames[maxphase], maxsub))
369 return maxphase
369 return maxphase
370 return commitphase
370 return commitphase
371
371
372 # subrepo classes need to implement the following abstract class:
372 # subrepo classes need to implement the following abstract class:
373
373
374 class abstractsubrepo(object):
374 class abstractsubrepo(object):
375
375
376 def __init__(self, ui):
376 def __init__(self, ui):
377 self.ui = ui
377 self.ui = ui
378
378
379 def storeclean(self, path):
379 def storeclean(self, path):
380 """
380 """
381 returns true if the repository has not changed since it was last
381 returns true if the repository has not changed since it was last
382 cloned from or pushed to a given repository.
382 cloned from or pushed to a given repository.
383 """
383 """
384 return False
384 return False
385
385
386 def dirty(self, ignoreupdate=False):
386 def dirty(self, ignoreupdate=False):
387 """returns true if the dirstate of the subrepo is dirty or does not
387 """returns true if the dirstate of the subrepo is dirty or does not
388 match current stored state. If ignoreupdate is true, only check
388 match current stored state. If ignoreupdate is true, only check
389 whether the subrepo has uncommitted changes in its dirstate.
389 whether the subrepo has uncommitted changes in its dirstate.
390 """
390 """
391 raise NotImplementedError
391 raise NotImplementedError
392
392
393 def basestate(self):
393 def basestate(self):
394 """current working directory base state, disregarding .hgsubstate
394 """current working directory base state, disregarding .hgsubstate
395 state and working directory modifications"""
395 state and working directory modifications"""
396 raise NotImplementedError
396 raise NotImplementedError
397
397
398 def checknested(self, path):
398 def checknested(self, path):
399 """check if path is a subrepository within this repository"""
399 """check if path is a subrepository within this repository"""
400 return False
400 return False
401
401
402 def commit(self, text, user, date):
402 def commit(self, text, user, date):
403 """commit the current changes to the subrepo with the given
403 """commit the current changes to the subrepo with the given
404 log message. Use given user and date if possible. Return the
404 log message. Use given user and date if possible. Return the
405 new state of the subrepo.
405 new state of the subrepo.
406 """
406 """
407 raise NotImplementedError
407 raise NotImplementedError
408
408
409 def phase(self, state):
409 def phase(self, state):
410 """returns phase of specified state in the subrepository.
410 """returns phase of specified state in the subrepository.
411 """
411 """
412 return phases.public
412 return phases.public
413
413
414 def remove(self):
414 def remove(self):
415 """remove the subrepo
415 """remove the subrepo
416
416
417 (should verify the dirstate is not dirty first)
417 (should verify the dirstate is not dirty first)
418 """
418 """
419 raise NotImplementedError
419 raise NotImplementedError
420
420
421 def get(self, state, overwrite=False):
421 def get(self, state, overwrite=False):
422 """run whatever commands are needed to put the subrepo into
422 """run whatever commands are needed to put the subrepo into
423 this state
423 this state
424 """
424 """
425 raise NotImplementedError
425 raise NotImplementedError
426
426
427 def merge(self, state):
427 def merge(self, state):
428 """merge currently-saved state with the new state."""
428 """merge currently-saved state with the new state."""
429 raise NotImplementedError
429 raise NotImplementedError
430
430
431 def push(self, opts):
431 def push(self, opts):
432 """perform whatever action is analogous to 'hg push'
432 """perform whatever action is analogous to 'hg push'
433
433
434 This may be a no-op on some systems.
434 This may be a no-op on some systems.
435 """
435 """
436 raise NotImplementedError
436 raise NotImplementedError
437
437
438 def add(self, ui, match, prefix, explicitonly, **opts):
438 def add(self, ui, match, prefix, explicitonly, **opts):
439 return []
439 return []
440
440
441 def addremove(self, matcher, prefix, opts, dry_run, similarity):
441 def addremove(self, matcher, prefix, opts, dry_run, similarity):
442 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
442 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
443 return 1
443 return 1
444
444
445 def cat(self, match, prefix, **opts):
445 def cat(self, match, prefix, **opts):
446 return 1
446 return 1
447
447
448 def status(self, rev2, **opts):
448 def status(self, rev2, **opts):
449 return scmutil.status([], [], [], [], [], [], [])
449 return scmutil.status([], [], [], [], [], [], [])
450
450
451 def diff(self, ui, diffopts, node2, match, prefix, **opts):
451 def diff(self, ui, diffopts, node2, match, prefix, **opts):
452 pass
452 pass
453
453
454 def outgoing(self, ui, dest, opts):
454 def outgoing(self, ui, dest, opts):
455 return 1
455 return 1
456
456
457 def incoming(self, ui, source, opts):
457 def incoming(self, ui, source, opts):
458 return 1
458 return 1
459
459
460 def files(self):
460 def files(self):
461 """return filename iterator"""
461 """return filename iterator"""
462 raise NotImplementedError
462 raise NotImplementedError
463
463
464 def filedata(self, name):
464 def filedata(self, name):
465 """return file data"""
465 """return file data"""
466 raise NotImplementedError
466 raise NotImplementedError
467
467
468 def fileflags(self, name):
468 def fileflags(self, name):
469 """return file flags"""
469 """return file flags"""
470 return ''
470 return ''
471
471
472 def archive(self, archiver, prefix, match=None):
472 def archive(self, archiver, prefix, match=None):
473 if match is not None:
473 if match is not None:
474 files = [f for f in self.files() if match(f)]
474 files = [f for f in self.files() if match(f)]
475 else:
475 else:
476 files = self.files()
476 files = self.files()
477 total = len(files)
477 total = len(files)
478 relpath = subrelpath(self)
478 relpath = subrelpath(self)
479 self.ui.progress(_('archiving (%s)') % relpath, 0,
479 self.ui.progress(_('archiving (%s)') % relpath, 0,
480 unit=_('files'), total=total)
480 unit=_('files'), total=total)
481 for i, name in enumerate(files):
481 for i, name in enumerate(files):
482 flags = self.fileflags(name)
482 flags = self.fileflags(name)
483 mode = 'x' in flags and 0755 or 0644
483 mode = 'x' in flags and 0755 or 0644
484 symlink = 'l' in flags
484 symlink = 'l' in flags
485 archiver.addfile(os.path.join(prefix, self._path, name),
485 archiver.addfile(os.path.join(prefix, self._path, name),
486 mode, symlink, self.filedata(name))
486 mode, symlink, self.filedata(name))
487 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
487 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
488 unit=_('files'), total=total)
488 unit=_('files'), total=total)
489 self.ui.progress(_('archiving (%s)') % relpath, None)
489 self.ui.progress(_('archiving (%s)') % relpath, None)
490 return total
490 return total
491
491
492 def walk(self, match):
492 def walk(self, match):
493 '''
493 '''
494 walk recursively through the directory tree, finding all files
494 walk recursively through the directory tree, finding all files
495 matched by the match function
495 matched by the match function
496 '''
496 '''
497 pass
497 pass
498
498
499 def forget(self, match, prefix):
499 def forget(self, match, prefix):
500 return ([], [])
500 return ([], [])
501
501
502 def removefiles(self, matcher, prefix, after, force, subrepos):
502 def removefiles(self, matcher, prefix, after, force, subrepos):
503 """remove the matched files from the subrepository and the filesystem,
503 """remove the matched files from the subrepository and the filesystem,
504 possibly by force and/or after the file has been removed from the
504 possibly by force and/or after the file has been removed from the
505 filesystem. Return 0 on success, 1 on any warning.
505 filesystem. Return 0 on success, 1 on any warning.
506 """
506 """
507 return 1
507 return 1
508
508
509 def revert(self, substate, *pats, **opts):
509 def revert(self, substate, *pats, **opts):
510 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
510 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
511 % (substate[0], substate[2]))
511 % (substate[0], substate[2]))
512 return []
512 return []
513
513
514 def shortid(self, revid):
514 def shortid(self, revid):
515 return revid
515 return revid
516
516
517 class hgsubrepo(abstractsubrepo):
517 class hgsubrepo(abstractsubrepo):
518 def __init__(self, ctx, path, state):
518 def __init__(self, ctx, path, state):
519 super(hgsubrepo, self).__init__(ctx._repo.ui)
519 super(hgsubrepo, self).__init__(ctx._repo.ui)
520 self._path = path
520 self._path = path
521 self._state = state
521 self._state = state
522 r = ctx._repo
522 r = ctx._repo
523 root = r.wjoin(path)
523 root = r.wjoin(path)
524 create = not r.wvfs.exists('%s/.hg' % path)
524 create = not r.wvfs.exists('%s/.hg' % path)
525 self._repo = hg.repository(r.baseui, root, create=create)
525 self._repo = hg.repository(r.baseui, root, create=create)
526 self.ui = self._repo.ui
526 self.ui = self._repo.ui
527 for s, k in [('ui', 'commitsubrepos')]:
527 for s, k in [('ui', 'commitsubrepos')]:
528 v = r.ui.config(s, k)
528 v = r.ui.config(s, k)
529 if v:
529 if v:
530 self.ui.setconfig(s, k, v, 'subrepo')
530 self.ui.setconfig(s, k, v, 'subrepo')
531 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
531 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
532 self._initrepo(r, state[0], create)
532 self._initrepo(r, state[0], create)
533
533
534 def storeclean(self, path):
534 def storeclean(self, path):
535 lock = self._repo.lock()
535 lock = self._repo.lock()
536 try:
536 try:
537 return self._storeclean(path)
537 return self._storeclean(path)
538 finally:
538 finally:
539 lock.release()
539 lock.release()
540
540
541 def _storeclean(self, path):
541 def _storeclean(self, path):
542 clean = True
542 clean = True
543 itercache = self._calcstorehash(path)
543 itercache = self._calcstorehash(path)
544 try:
544 try:
545 for filehash in self._readstorehashcache(path):
545 for filehash in self._readstorehashcache(path):
546 if filehash != itercache.next():
546 if filehash != itercache.next():
547 clean = False
547 clean = False
548 break
548 break
549 except StopIteration:
549 except StopIteration:
550 # the cached and current pull states have a different size
550 # the cached and current pull states have a different size
551 clean = False
551 clean = False
552 if clean:
552 if clean:
553 try:
553 try:
554 itercache.next()
554 itercache.next()
555 # the cached and current pull states have a different size
555 # the cached and current pull states have a different size
556 clean = False
556 clean = False
557 except StopIteration:
557 except StopIteration:
558 pass
558 pass
559 return clean
559 return clean
560
560
561 def _calcstorehash(self, remotepath):
561 def _calcstorehash(self, remotepath):
562 '''calculate a unique "store hash"
562 '''calculate a unique "store hash"
563
563
564 This method is used to to detect when there are changes that may
564 This method is used to to detect when there are changes that may
565 require a push to a given remote path.'''
565 require a push to a given remote path.'''
566 # sort the files that will be hashed in increasing (likely) file size
566 # sort the files that will be hashed in increasing (likely) file size
567 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
567 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
568 yield '# %s\n' % _expandedabspath(remotepath)
568 yield '# %s\n' % _expandedabspath(remotepath)
569 vfs = self._repo.vfs
569 vfs = self._repo.vfs
570 for relname in filelist:
570 for relname in filelist:
571 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
571 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
572 yield '%s = %s\n' % (relname, filehash)
572 yield '%s = %s\n' % (relname, filehash)
573
573
574 @propertycache
574 @propertycache
575 def _cachestorehashvfs(self):
575 def _cachestorehashvfs(self):
576 return scmutil.vfs(self._repo.join('cache/storehash'))
576 return scmutil.vfs(self._repo.join('cache/storehash'))
577
577
578 def _readstorehashcache(self, remotepath):
578 def _readstorehashcache(self, remotepath):
579 '''read the store hash cache for a given remote repository'''
579 '''read the store hash cache for a given remote repository'''
580 cachefile = _getstorehashcachename(remotepath)
580 cachefile = _getstorehashcachename(remotepath)
581 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
581 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
582
582
583 def _cachestorehash(self, remotepath):
583 def _cachestorehash(self, remotepath):
584 '''cache the current store hash
584 '''cache the current store hash
585
585
586 Each remote repo requires its own store hash cache, because a subrepo
586 Each remote repo requires its own store hash cache, because a subrepo
587 store may be "clean" versus a given remote repo, but not versus another
587 store may be "clean" versus a given remote repo, but not versus another
588 '''
588 '''
589 cachefile = _getstorehashcachename(remotepath)
589 cachefile = _getstorehashcachename(remotepath)
590 lock = self._repo.lock()
590 lock = self._repo.lock()
591 try:
591 try:
592 storehash = list(self._calcstorehash(remotepath))
592 storehash = list(self._calcstorehash(remotepath))
593 vfs = self._cachestorehashvfs
593 vfs = self._cachestorehashvfs
594 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
594 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
595 finally:
595 finally:
596 lock.release()
596 lock.release()
597
597
598 @annotatesubrepoerror
598 @annotatesubrepoerror
599 def _initrepo(self, parentrepo, source, create):
599 def _initrepo(self, parentrepo, source, create):
600 self._repo._subparent = parentrepo
600 self._repo._subparent = parentrepo
601 self._repo._subsource = source
601 self._repo._subsource = source
602
602
603 if create:
603 if create:
604 lines = ['[paths]\n']
604 lines = ['[paths]\n']
605
605
606 def addpathconfig(key, value):
606 def addpathconfig(key, value):
607 if value:
607 if value:
608 lines.append('%s = %s\n' % (key, value))
608 lines.append('%s = %s\n' % (key, value))
609 self.ui.setconfig('paths', key, value, 'subrepo')
609 self.ui.setconfig('paths', key, value, 'subrepo')
610
610
611 defpath = _abssource(self._repo, abort=False)
611 defpath = _abssource(self._repo, abort=False)
612 defpushpath = _abssource(self._repo, True, abort=False)
612 defpushpath = _abssource(self._repo, True, abort=False)
613 addpathconfig('default', defpath)
613 addpathconfig('default', defpath)
614 if defpath != defpushpath:
614 if defpath != defpushpath:
615 addpathconfig('default-push', defpushpath)
615 addpathconfig('default-push', defpushpath)
616
616
617 fp = self._repo.vfs("hgrc", "w", text=True)
617 fp = self._repo.vfs("hgrc", "w", text=True)
618 try:
618 try:
619 fp.write(''.join(lines))
619 fp.write(''.join(lines))
620 finally:
620 finally:
621 fp.close()
621 fp.close()
622
622
623 @annotatesubrepoerror
623 @annotatesubrepoerror
624 def add(self, ui, match, prefix, explicitonly, **opts):
624 def add(self, ui, match, prefix, explicitonly, **opts):
625 return cmdutil.add(ui, self._repo, match,
625 return cmdutil.add(ui, self._repo, match,
626 os.path.join(prefix, self._path), explicitonly,
626 os.path.join(prefix, self._path), explicitonly,
627 **opts)
627 **opts)
628
628
629 @annotatesubrepoerror
629 @annotatesubrepoerror
630 def addremove(self, m, prefix, opts, dry_run, similarity):
630 def addremove(self, m, prefix, opts, dry_run, similarity):
631 # In the same way as sub directories are processed, once in a subrepo,
631 # In the same way as sub directories are processed, once in a subrepo,
632 # always entry any of its subrepos. Don't corrupt the options that will
632 # always entry any of its subrepos. Don't corrupt the options that will
633 # be used to process sibling subrepos however.
633 # be used to process sibling subrepos however.
634 opts = copy.copy(opts)
634 opts = copy.copy(opts)
635 opts['subrepos'] = True
635 opts['subrepos'] = True
636 return scmutil.addremove(self._repo, m,
636 return scmutil.addremove(self._repo, m,
637 os.path.join(prefix, self._path), opts,
637 os.path.join(prefix, self._path), opts,
638 dry_run, similarity)
638 dry_run, similarity)
639
639
640 @annotatesubrepoerror
640 @annotatesubrepoerror
641 def cat(self, match, prefix, **opts):
641 def cat(self, match, prefix, **opts):
642 rev = self._state[1]
642 rev = self._state[1]
643 ctx = self._repo[rev]
643 ctx = self._repo[rev]
644 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
644 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
645
645
646 @annotatesubrepoerror
646 @annotatesubrepoerror
647 def status(self, rev2, **opts):
647 def status(self, rev2, **opts):
648 try:
648 try:
649 rev1 = self._state[1]
649 rev1 = self._state[1]
650 ctx1 = self._repo[rev1]
650 ctx1 = self._repo[rev1]
651 ctx2 = self._repo[rev2]
651 ctx2 = self._repo[rev2]
652 return self._repo.status(ctx1, ctx2, **opts)
652 return self._repo.status(ctx1, ctx2, **opts)
653 except error.RepoLookupError, inst:
653 except error.RepoLookupError, inst:
654 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
654 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
655 % (inst, subrelpath(self)))
655 % (inst, subrelpath(self)))
656 return scmutil.status([], [], [], [], [], [], [])
656 return scmutil.status([], [], [], [], [], [], [])
657
657
658 @annotatesubrepoerror
658 @annotatesubrepoerror
659 def diff(self, ui, diffopts, node2, match, prefix, **opts):
659 def diff(self, ui, diffopts, node2, match, prefix, **opts):
660 try:
660 try:
661 node1 = node.bin(self._state[1])
661 node1 = node.bin(self._state[1])
662 # We currently expect node2 to come from substate and be
662 # We currently expect node2 to come from substate and be
663 # in hex format
663 # in hex format
664 if node2 is not None:
664 if node2 is not None:
665 node2 = node.bin(node2)
665 node2 = node.bin(node2)
666 cmdutil.diffordiffstat(ui, self._repo, diffopts,
666 cmdutil.diffordiffstat(ui, self._repo, diffopts,
667 node1, node2, match,
667 node1, node2, match,
668 prefix=posixpath.join(prefix, self._path),
668 prefix=posixpath.join(prefix, self._path),
669 listsubrepos=True, **opts)
669 listsubrepos=True, **opts)
670 except error.RepoLookupError, inst:
670 except error.RepoLookupError, inst:
671 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
671 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
672 % (inst, subrelpath(self)))
672 % (inst, subrelpath(self)))
673
673
674 @annotatesubrepoerror
674 @annotatesubrepoerror
675 def archive(self, archiver, prefix, match=None):
675 def archive(self, archiver, prefix, match=None):
676 self._get(self._state + ('hg',))
676 self._get(self._state + ('hg',))
677 total = abstractsubrepo.archive(self, archiver, prefix, match)
677 total = abstractsubrepo.archive(self, archiver, prefix, match)
678 rev = self._state[1]
678 rev = self._state[1]
679 ctx = self._repo[rev]
679 ctx = self._repo[rev]
680 for subpath in ctx.substate:
680 for subpath in ctx.substate:
681 s = subrepo(ctx, subpath)
681 s = subrepo(ctx, subpath)
682 submatch = matchmod.narrowmatcher(subpath, match)
682 submatch = matchmod.narrowmatcher(subpath, match)
683 total += s.archive(
683 total += s.archive(
684 archiver, os.path.join(prefix, self._path), submatch)
684 archiver, os.path.join(prefix, self._path), submatch)
685 return total
685 return total
686
686
687 @annotatesubrepoerror
687 @annotatesubrepoerror
688 def dirty(self, ignoreupdate=False):
688 def dirty(self, ignoreupdate=False):
689 r = self._state[1]
689 r = self._state[1]
690 if r == '' and not ignoreupdate: # no state recorded
690 if r == '' and not ignoreupdate: # no state recorded
691 return True
691 return True
692 w = self._repo[None]
692 w = self._repo[None]
693 if r != w.p1().hex() and not ignoreupdate:
693 if r != w.p1().hex() and not ignoreupdate:
694 # different version checked out
694 # different version checked out
695 return True
695 return True
696 return w.dirty() # working directory changed
696 return w.dirty() # working directory changed
697
697
698 def basestate(self):
698 def basestate(self):
699 return self._repo['.'].hex()
699 return self._repo['.'].hex()
700
700
701 def checknested(self, path):
701 def checknested(self, path):
702 return self._repo._checknested(self._repo.wjoin(path))
702 return self._repo._checknested(self._repo.wjoin(path))
703
703
704 @annotatesubrepoerror
704 @annotatesubrepoerror
705 def commit(self, text, user, date):
705 def commit(self, text, user, date):
706 # don't bother committing in the subrepo if it's only been
706 # don't bother committing in the subrepo if it's only been
707 # updated
707 # updated
708 if not self.dirty(True):
708 if not self.dirty(True):
709 return self._repo['.'].hex()
709 return self._repo['.'].hex()
710 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
710 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
711 n = self._repo.commit(text, user, date)
711 n = self._repo.commit(text, user, date)
712 if not n:
712 if not n:
713 return self._repo['.'].hex() # different version checked out
713 return self._repo['.'].hex() # different version checked out
714 return node.hex(n)
714 return node.hex(n)
715
715
716 @annotatesubrepoerror
716 @annotatesubrepoerror
717 def phase(self, state):
717 def phase(self, state):
718 return self._repo[state].phase()
718 return self._repo[state].phase()
719
719
720 @annotatesubrepoerror
720 @annotatesubrepoerror
721 def remove(self):
721 def remove(self):
722 # we can't fully delete the repository as it may contain
722 # we can't fully delete the repository as it may contain
723 # local-only history
723 # local-only history
724 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
724 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
725 hg.clean(self._repo, node.nullid, False)
725 hg.clean(self._repo, node.nullid, False)
726
726
727 def _get(self, state):
727 def _get(self, state):
728 source, revision, kind = state
728 source, revision, kind = state
729 if revision in self._repo.unfiltered():
729 if revision in self._repo.unfiltered():
730 return True
730 return True
731 self._repo._subsource = source
731 self._repo._subsource = source
732 srcurl = _abssource(self._repo)
732 srcurl = _abssource(self._repo)
733 other = hg.peer(self._repo, {}, srcurl)
733 other = hg.peer(self._repo, {}, srcurl)
734 if len(self._repo) == 0:
734 if len(self._repo) == 0:
735 self.ui.status(_('cloning subrepo %s from %s\n')
735 self.ui.status(_('cloning subrepo %s from %s\n')
736 % (subrelpath(self), srcurl))
736 % (subrelpath(self), srcurl))
737 parentrepo = self._repo._subparent
737 parentrepo = self._repo._subparent
738 shutil.rmtree(self._repo.path)
738 shutil.rmtree(self._repo.path)
739 other, cloned = hg.clone(self._repo._subparent.baseui, {},
739 other, cloned = hg.clone(self._repo._subparent.baseui, {},
740 other, self._repo.root,
740 other, self._repo.root,
741 update=False)
741 update=False)
742 self._repo = cloned.local()
742 self._repo = cloned.local()
743 self._initrepo(parentrepo, source, create=True)
743 self._initrepo(parentrepo, source, create=True)
744 self._cachestorehash(srcurl)
744 self._cachestorehash(srcurl)
745 else:
745 else:
746 self.ui.status(_('pulling subrepo %s from %s\n')
746 self.ui.status(_('pulling subrepo %s from %s\n')
747 % (subrelpath(self), srcurl))
747 % (subrelpath(self), srcurl))
748 cleansub = self.storeclean(srcurl)
748 cleansub = self.storeclean(srcurl)
749 exchange.pull(self._repo, other)
749 exchange.pull(self._repo, other)
750 if cleansub:
750 if cleansub:
751 # keep the repo clean after pull
751 # keep the repo clean after pull
752 self._cachestorehash(srcurl)
752 self._cachestorehash(srcurl)
753 return False
753 return False
754
754
755 @annotatesubrepoerror
755 @annotatesubrepoerror
756 def get(self, state, overwrite=False):
756 def get(self, state, overwrite=False):
757 inrepo = self._get(state)
757 inrepo = self._get(state)
758 source, revision, kind = state
758 source, revision, kind = state
759 repo = self._repo
759 repo = self._repo
760 repo.ui.debug("getting subrepo %s\n" % self._path)
760 repo.ui.debug("getting subrepo %s\n" % self._path)
761 if inrepo:
761 if inrepo:
762 urepo = repo.unfiltered()
762 urepo = repo.unfiltered()
763 ctx = urepo[revision]
763 ctx = urepo[revision]
764 if ctx.hidden():
764 if ctx.hidden():
765 urepo.ui.warn(
765 urepo.ui.warn(
766 _('revision %s in subrepo %s is hidden\n') \
766 _('revision %s in subrepo %s is hidden\n') \
767 % (revision[0:12], self._path))
767 % (revision[0:12], self._path))
768 repo = urepo
768 repo = urepo
769 hg.updaterepo(repo, revision, overwrite)
769 hg.updaterepo(repo, revision, overwrite)
770
770
771 @annotatesubrepoerror
771 @annotatesubrepoerror
772 def merge(self, state):
772 def merge(self, state):
773 self._get(state)
773 self._get(state)
774 cur = self._repo['.']
774 cur = self._repo['.']
775 dst = self._repo[state[1]]
775 dst = self._repo[state[1]]
776 anc = dst.ancestor(cur)
776 anc = dst.ancestor(cur)
777
777
778 def mergefunc():
778 def mergefunc():
779 if anc == cur and dst.branch() == cur.branch():
779 if anc == cur and dst.branch() == cur.branch():
780 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
780 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
781 hg.update(self._repo, state[1])
781 hg.update(self._repo, state[1])
782 elif anc == dst:
782 elif anc == dst:
783 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
783 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
784 else:
784 else:
785 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
785 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
786 hg.merge(self._repo, state[1], remind=False)
786 hg.merge(self._repo, state[1], remind=False)
787
787
788 wctx = self._repo[None]
788 wctx = self._repo[None]
789 if self.dirty():
789 if self.dirty():
790 if anc != dst:
790 if anc != dst:
791 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
791 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
792 mergefunc()
792 mergefunc()
793 else:
793 else:
794 mergefunc()
794 mergefunc()
795 else:
795 else:
796 mergefunc()
796 mergefunc()
797
797
798 @annotatesubrepoerror
798 @annotatesubrepoerror
799 def push(self, opts):
799 def push(self, opts):
800 force = opts.get('force')
800 force = opts.get('force')
801 newbranch = opts.get('new_branch')
801 newbranch = opts.get('new_branch')
802 ssh = opts.get('ssh')
802 ssh = opts.get('ssh')
803
803
804 # push subrepos depth-first for coherent ordering
804 # push subrepos depth-first for coherent ordering
805 c = self._repo['']
805 c = self._repo['']
806 subs = c.substate # only repos that are committed
806 subs = c.substate # only repos that are committed
807 for s in sorted(subs):
807 for s in sorted(subs):
808 if c.sub(s).push(opts) == 0:
808 if c.sub(s).push(opts) == 0:
809 return False
809 return False
810
810
811 dsturl = _abssource(self._repo, True)
811 dsturl = _abssource(self._repo, True)
812 if not force:
812 if not force:
813 if self.storeclean(dsturl):
813 if self.storeclean(dsturl):
814 self.ui.status(
814 self.ui.status(
815 _('no changes made to subrepo %s since last push to %s\n')
815 _('no changes made to subrepo %s since last push to %s\n')
816 % (subrelpath(self), dsturl))
816 % (subrelpath(self), dsturl))
817 return None
817 return None
818 self.ui.status(_('pushing subrepo %s to %s\n') %
818 self.ui.status(_('pushing subrepo %s to %s\n') %
819 (subrelpath(self), dsturl))
819 (subrelpath(self), dsturl))
820 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
820 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
821 res = exchange.push(self._repo, other, force, newbranch=newbranch)
821 res = exchange.push(self._repo, other, force, newbranch=newbranch)
822
822
823 # the repo is now clean
823 # the repo is now clean
824 self._cachestorehash(dsturl)
824 self._cachestorehash(dsturl)
825 return res.cgresult
825 return res.cgresult
826
826
827 @annotatesubrepoerror
827 @annotatesubrepoerror
828 def outgoing(self, ui, dest, opts):
828 def outgoing(self, ui, dest, opts):
829 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
829 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
830
830
831 @annotatesubrepoerror
831 @annotatesubrepoerror
832 def incoming(self, ui, source, opts):
832 def incoming(self, ui, source, opts):
833 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
833 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
834
834
835 @annotatesubrepoerror
835 @annotatesubrepoerror
836 def files(self):
836 def files(self):
837 rev = self._state[1]
837 rev = self._state[1]
838 ctx = self._repo[rev]
838 ctx = self._repo[rev]
839 return ctx.manifest().keys()
839 return ctx.manifest().keys()
840
840
841 def filedata(self, name):
841 def filedata(self, name):
842 rev = self._state[1]
842 rev = self._state[1]
843 return self._repo[rev][name].data()
843 return self._repo[rev][name].data()
844
844
845 def fileflags(self, name):
845 def fileflags(self, name):
846 rev = self._state[1]
846 rev = self._state[1]
847 ctx = self._repo[rev]
847 ctx = self._repo[rev]
848 return ctx.flags(name)
848 return ctx.flags(name)
849
849
850 def walk(self, match):
850 def walk(self, match):
851 ctx = self._repo[None]
851 ctx = self._repo[None]
852 return ctx.walk(match)
852 return ctx.walk(match)
853
853
854 @annotatesubrepoerror
854 @annotatesubrepoerror
855 def forget(self, match, prefix):
855 def forget(self, match, prefix):
856 return cmdutil.forget(self.ui, self._repo, match,
856 return cmdutil.forget(self.ui, self._repo, match,
857 os.path.join(prefix, self._path), True)
857 os.path.join(prefix, self._path), True)
858
858
859 @annotatesubrepoerror
859 @annotatesubrepoerror
860 def removefiles(self, matcher, prefix, after, force, subrepos):
860 def removefiles(self, matcher, prefix, after, force, subrepos):
861 return cmdutil.remove(self.ui, self._repo, matcher,
861 return cmdutil.remove(self.ui, self._repo, matcher,
862 os.path.join(prefix, self._path), after, force,
862 os.path.join(prefix, self._path), after, force,
863 subrepos)
863 subrepos)
864
864
865 @annotatesubrepoerror
865 @annotatesubrepoerror
866 def revert(self, substate, *pats, **opts):
866 def revert(self, substate, *pats, **opts):
867 # reverting a subrepo is a 2 step process:
867 # reverting a subrepo is a 2 step process:
868 # 1. if the no_backup is not set, revert all modified
868 # 1. if the no_backup is not set, revert all modified
869 # files inside the subrepo
869 # files inside the subrepo
870 # 2. update the subrepo to the revision specified in
870 # 2. update the subrepo to the revision specified in
871 # the corresponding substate dictionary
871 # the corresponding substate dictionary
872 self.ui.status(_('reverting subrepo %s\n') % substate[0])
872 self.ui.status(_('reverting subrepo %s\n') % substate[0])
873 if not opts.get('no_backup'):
873 if not opts.get('no_backup'):
874 # Revert all files on the subrepo, creating backups
874 # Revert all files on the subrepo, creating backups
875 # Note that this will not recursively revert subrepos
875 # Note that this will not recursively revert subrepos
876 # We could do it if there was a set:subrepos() predicate
876 # We could do it if there was a set:subrepos() predicate
877 opts = opts.copy()
877 opts = opts.copy()
878 opts['date'] = None
878 opts['date'] = None
879 opts['rev'] = substate[1]
879 opts['rev'] = substate[1]
880
880
881 self.filerevert(*pats, **opts)
881 self.filerevert(*pats, **opts)
882
882
883 # Update the repo to the revision specified in the given substate
883 # Update the repo to the revision specified in the given substate
884 if not opts.get('dry_run'):
884 if not opts.get('dry_run'):
885 self.get(substate, overwrite=True)
885 self.get(substate, overwrite=True)
886
886
887 def filerevert(self, *pats, **opts):
887 def filerevert(self, *pats, **opts):
888 ctx = self._repo[opts['rev']]
888 ctx = self._repo[opts['rev']]
889 parents = self._repo.dirstate.parents()
889 parents = self._repo.dirstate.parents()
890 if opts.get('all'):
890 if opts.get('all'):
891 pats = ['set:modified()']
891 pats = ['set:modified()']
892 else:
892 else:
893 pats = []
893 pats = []
894 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
894 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
895
895
896 def shortid(self, revid):
896 def shortid(self, revid):
897 return revid[:12]
897 return revid[:12]
898
898
899 class svnsubrepo(abstractsubrepo):
899 class svnsubrepo(abstractsubrepo):
900 def __init__(self, ctx, path, state):
900 def __init__(self, ctx, path, state):
901 super(svnsubrepo, self).__init__(ctx._repo.ui)
901 super(svnsubrepo, self).__init__(ctx._repo.ui)
902 self._path = path
902 self._path = path
903 self._state = state
903 self._state = state
904 self._ctx = ctx
904 self._ctx = ctx
905 self._exe = util.findexe('svn')
905 self._exe = util.findexe('svn')
906 if not self._exe:
906 if not self._exe:
907 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
907 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
908 % self._path)
908 % self._path)
909
909
910 def _svncommand(self, commands, filename='', failok=False):
910 def _svncommand(self, commands, filename='', failok=False):
911 cmd = [self._exe]
911 cmd = [self._exe]
912 extrakw = {}
912 extrakw = {}
913 if not self.ui.interactive():
913 if not self.ui.interactive():
914 # Making stdin be a pipe should prevent svn from behaving
914 # Making stdin be a pipe should prevent svn from behaving
915 # interactively even if we can't pass --non-interactive.
915 # interactively even if we can't pass --non-interactive.
916 extrakw['stdin'] = subprocess.PIPE
916 extrakw['stdin'] = subprocess.PIPE
917 # Starting in svn 1.5 --non-interactive is a global flag
917 # Starting in svn 1.5 --non-interactive is a global flag
918 # instead of being per-command, but we need to support 1.4 so
918 # instead of being per-command, but we need to support 1.4 so
919 # we have to be intelligent about what commands take
919 # we have to be intelligent about what commands take
920 # --non-interactive.
920 # --non-interactive.
921 if commands[0] in ('update', 'checkout', 'commit'):
921 if commands[0] in ('update', 'checkout', 'commit'):
922 cmd.append('--non-interactive')
922 cmd.append('--non-interactive')
923 cmd.extend(commands)
923 cmd.extend(commands)
924 if filename is not None:
924 if filename is not None:
925 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
925 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
926 cmd.append(path)
926 cmd.append(path)
927 env = dict(os.environ)
927 env = dict(os.environ)
928 # Avoid localized output, preserve current locale for everything else.
928 # Avoid localized output, preserve current locale for everything else.
929 lc_all = env.get('LC_ALL')
929 lc_all = env.get('LC_ALL')
930 if lc_all:
930 if lc_all:
931 env['LANG'] = lc_all
931 env['LANG'] = lc_all
932 del env['LC_ALL']
932 del env['LC_ALL']
933 env['LC_MESSAGES'] = 'C'
933 env['LC_MESSAGES'] = 'C'
934 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
934 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
935 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
935 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
936 universal_newlines=True, env=env, **extrakw)
936 universal_newlines=True, env=env, **extrakw)
937 stdout, stderr = p.communicate()
937 stdout, stderr = p.communicate()
938 stderr = stderr.strip()
938 stderr = stderr.strip()
939 if not failok:
939 if not failok:
940 if p.returncode:
940 if p.returncode:
941 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
941 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
942 if stderr:
942 if stderr:
943 self.ui.warn(stderr + '\n')
943 self.ui.warn(stderr + '\n')
944 return stdout, stderr
944 return stdout, stderr
945
945
946 @propertycache
946 @propertycache
947 def _svnversion(self):
947 def _svnversion(self):
948 output, err = self._svncommand(['--version', '--quiet'], filename=None)
948 output, err = self._svncommand(['--version', '--quiet'], filename=None)
949 m = re.search(r'^(\d+)\.(\d+)', output)
949 m = re.search(r'^(\d+)\.(\d+)', output)
950 if not m:
950 if not m:
951 raise util.Abort(_('cannot retrieve svn tool version'))
951 raise util.Abort(_('cannot retrieve svn tool version'))
952 return (int(m.group(1)), int(m.group(2)))
952 return (int(m.group(1)), int(m.group(2)))
953
953
954 def _wcrevs(self):
954 def _wcrevs(self):
955 # Get the working directory revision as well as the last
955 # Get the working directory revision as well as the last
956 # commit revision so we can compare the subrepo state with
956 # commit revision so we can compare the subrepo state with
957 # both. We used to store the working directory one.
957 # both. We used to store the working directory one.
958 output, err = self._svncommand(['info', '--xml'])
958 output, err = self._svncommand(['info', '--xml'])
959 doc = xml.dom.minidom.parseString(output)
959 doc = xml.dom.minidom.parseString(output)
960 entries = doc.getElementsByTagName('entry')
960 entries = doc.getElementsByTagName('entry')
961 lastrev, rev = '0', '0'
961 lastrev, rev = '0', '0'
962 if entries:
962 if entries:
963 rev = str(entries[0].getAttribute('revision')) or '0'
963 rev = str(entries[0].getAttribute('revision')) or '0'
964 commits = entries[0].getElementsByTagName('commit')
964 commits = entries[0].getElementsByTagName('commit')
965 if commits:
965 if commits:
966 lastrev = str(commits[0].getAttribute('revision')) or '0'
966 lastrev = str(commits[0].getAttribute('revision')) or '0'
967 return (lastrev, rev)
967 return (lastrev, rev)
968
968
969 def _wcrev(self):
969 def _wcrev(self):
970 return self._wcrevs()[0]
970 return self._wcrevs()[0]
971
971
972 def _wcchanged(self):
972 def _wcchanged(self):
973 """Return (changes, extchanges, missing) where changes is True
973 """Return (changes, extchanges, missing) where changes is True
974 if the working directory was changed, extchanges is
974 if the working directory was changed, extchanges is
975 True if any of these changes concern an external entry and missing
975 True if any of these changes concern an external entry and missing
976 is True if any change is a missing entry.
976 is True if any change is a missing entry.
977 """
977 """
978 output, err = self._svncommand(['status', '--xml'])
978 output, err = self._svncommand(['status', '--xml'])
979 externals, changes, missing = [], [], []
979 externals, changes, missing = [], [], []
980 doc = xml.dom.minidom.parseString(output)
980 doc = xml.dom.minidom.parseString(output)
981 for e in doc.getElementsByTagName('entry'):
981 for e in doc.getElementsByTagName('entry'):
982 s = e.getElementsByTagName('wc-status')
982 s = e.getElementsByTagName('wc-status')
983 if not s:
983 if not s:
984 continue
984 continue
985 item = s[0].getAttribute('item')
985 item = s[0].getAttribute('item')
986 props = s[0].getAttribute('props')
986 props = s[0].getAttribute('props')
987 path = e.getAttribute('path')
987 path = e.getAttribute('path')
988 if item == 'external':
988 if item == 'external':
989 externals.append(path)
989 externals.append(path)
990 elif item == 'missing':
990 elif item == 'missing':
991 missing.append(path)
991 missing.append(path)
992 if (item not in ('', 'normal', 'unversioned', 'external')
992 if (item not in ('', 'normal', 'unversioned', 'external')
993 or props not in ('', 'none', 'normal')):
993 or props not in ('', 'none', 'normal')):
994 changes.append(path)
994 changes.append(path)
995 for path in changes:
995 for path in changes:
996 for ext in externals:
996 for ext in externals:
997 if path == ext or path.startswith(ext + os.sep):
997 if path == ext or path.startswith(ext + os.sep):
998 return True, True, bool(missing)
998 return True, True, bool(missing)
999 return bool(changes), False, bool(missing)
999 return bool(changes), False, bool(missing)
1000
1000
1001 def dirty(self, ignoreupdate=False):
1001 def dirty(self, ignoreupdate=False):
1002 if not self._wcchanged()[0]:
1002 if not self._wcchanged()[0]:
1003 if self._state[1] in self._wcrevs() or ignoreupdate:
1003 if self._state[1] in self._wcrevs() or ignoreupdate:
1004 return False
1004 return False
1005 return True
1005 return True
1006
1006
1007 def basestate(self):
1007 def basestate(self):
1008 lastrev, rev = self._wcrevs()
1008 lastrev, rev = self._wcrevs()
1009 if lastrev != rev:
1009 if lastrev != rev:
1010 # Last committed rev is not the same than rev. We would
1010 # Last committed rev is not the same than rev. We would
1011 # like to take lastrev but we do not know if the subrepo
1011 # like to take lastrev but we do not know if the subrepo
1012 # URL exists at lastrev. Test it and fallback to rev it
1012 # URL exists at lastrev. Test it and fallback to rev it
1013 # is not there.
1013 # is not there.
1014 try:
1014 try:
1015 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1015 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1016 return lastrev
1016 return lastrev
1017 except error.Abort:
1017 except error.Abort:
1018 pass
1018 pass
1019 return rev
1019 return rev
1020
1020
1021 @annotatesubrepoerror
1021 @annotatesubrepoerror
1022 def commit(self, text, user, date):
1022 def commit(self, text, user, date):
1023 # user and date are out of our hands since svn is centralized
1023 # user and date are out of our hands since svn is centralized
1024 changed, extchanged, missing = self._wcchanged()
1024 changed, extchanged, missing = self._wcchanged()
1025 if not changed:
1025 if not changed:
1026 return self.basestate()
1026 return self.basestate()
1027 if extchanged:
1027 if extchanged:
1028 # Do not try to commit externals
1028 # Do not try to commit externals
1029 raise util.Abort(_('cannot commit svn externals'))
1029 raise util.Abort(_('cannot commit svn externals'))
1030 if missing:
1030 if missing:
1031 # svn can commit with missing entries but aborting like hg
1031 # svn can commit with missing entries but aborting like hg
1032 # seems a better approach.
1032 # seems a better approach.
1033 raise util.Abort(_('cannot commit missing svn entries'))
1033 raise util.Abort(_('cannot commit missing svn entries'))
1034 commitinfo, err = self._svncommand(['commit', '-m', text])
1034 commitinfo, err = self._svncommand(['commit', '-m', text])
1035 self.ui.status(commitinfo)
1035 self.ui.status(commitinfo)
1036 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1036 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1037 if not newrev:
1037 if not newrev:
1038 if not commitinfo.strip():
1038 if not commitinfo.strip():
1039 # Sometimes, our definition of "changed" differs from
1039 # Sometimes, our definition of "changed" differs from
1040 # svn one. For instance, svn ignores missing files
1040 # svn one. For instance, svn ignores missing files
1041 # when committing. If there are only missing files, no
1041 # when committing. If there are only missing files, no
1042 # commit is made, no output and no error code.
1042 # commit is made, no output and no error code.
1043 raise util.Abort(_('failed to commit svn changes'))
1043 raise util.Abort(_('failed to commit svn changes'))
1044 raise util.Abort(commitinfo.splitlines()[-1])
1044 raise util.Abort(commitinfo.splitlines()[-1])
1045 newrev = newrev.groups()[0]
1045 newrev = newrev.groups()[0]
1046 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1046 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1047 return newrev
1047 return newrev
1048
1048
1049 @annotatesubrepoerror
1049 @annotatesubrepoerror
1050 def remove(self):
1050 def remove(self):
1051 if self.dirty():
1051 if self.dirty():
1052 self.ui.warn(_('not removing repo %s because '
1052 self.ui.warn(_('not removing repo %s because '
1053 'it has changes.\n') % self._path)
1053 'it has changes.\n') % self._path)
1054 return
1054 return
1055 self.ui.note(_('removing subrepo %s\n') % self._path)
1055 self.ui.note(_('removing subrepo %s\n') % self._path)
1056
1056
1057 def onerror(function, path, excinfo):
1057 def onerror(function, path, excinfo):
1058 if function is not os.remove:
1058 if function is not os.remove:
1059 raise
1059 raise
1060 # read-only files cannot be unlinked under Windows
1060 # read-only files cannot be unlinked under Windows
1061 s = os.stat(path)
1061 s = os.stat(path)
1062 if (s.st_mode & stat.S_IWRITE) != 0:
1062 if (s.st_mode & stat.S_IWRITE) != 0:
1063 raise
1063 raise
1064 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
1064 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
1065 os.remove(path)
1065 os.remove(path)
1066
1066
1067 path = self._ctx._repo.wjoin(self._path)
1067 path = self._ctx._repo.wjoin(self._path)
1068 shutil.rmtree(path, onerror=onerror)
1068 shutil.rmtree(path, onerror=onerror)
1069 try:
1069 try:
1070 os.removedirs(os.path.dirname(path))
1070 os.removedirs(os.path.dirname(path))
1071 except OSError:
1071 except OSError:
1072 pass
1072 pass
1073
1073
1074 @annotatesubrepoerror
1074 @annotatesubrepoerror
1075 def get(self, state, overwrite=False):
1075 def get(self, state, overwrite=False):
1076 if overwrite:
1076 if overwrite:
1077 self._svncommand(['revert', '--recursive'])
1077 self._svncommand(['revert', '--recursive'])
1078 args = ['checkout']
1078 args = ['checkout']
1079 if self._svnversion >= (1, 5):
1079 if self._svnversion >= (1, 5):
1080 args.append('--force')
1080 args.append('--force')
1081 # The revision must be specified at the end of the URL to properly
1081 # The revision must be specified at the end of the URL to properly
1082 # update to a directory which has since been deleted and recreated.
1082 # update to a directory which has since been deleted and recreated.
1083 args.append('%s@%s' % (state[0], state[1]))
1083 args.append('%s@%s' % (state[0], state[1]))
1084 status, err = self._svncommand(args, failok=True)
1084 status, err = self._svncommand(args, failok=True)
1085 _sanitize(self.ui, self._ctx._repo.wjoin(self._path), '.svn')
1085 _sanitize(self.ui, self._ctx._repo.wjoin(self._path), '.svn')
1086 if not re.search('Checked out revision [0-9]+.', status):
1086 if not re.search('Checked out revision [0-9]+.', status):
1087 if ('is already a working copy for a different URL' in err
1087 if ('is already a working copy for a different URL' in err
1088 and (self._wcchanged()[:2] == (False, False))):
1088 and (self._wcchanged()[:2] == (False, False))):
1089 # obstructed but clean working copy, so just blow it away.
1089 # obstructed but clean working copy, so just blow it away.
1090 self.remove()
1090 self.remove()
1091 self.get(state, overwrite=False)
1091 self.get(state, overwrite=False)
1092 return
1092 return
1093 raise util.Abort((status or err).splitlines()[-1])
1093 raise util.Abort((status or err).splitlines()[-1])
1094 self.ui.status(status)
1094 self.ui.status(status)
1095
1095
1096 @annotatesubrepoerror
1096 @annotatesubrepoerror
1097 def merge(self, state):
1097 def merge(self, state):
1098 old = self._state[1]
1098 old = self._state[1]
1099 new = state[1]
1099 new = state[1]
1100 wcrev = self._wcrev()
1100 wcrev = self._wcrev()
1101 if new != wcrev:
1101 if new != wcrev:
1102 dirty = old == wcrev or self._wcchanged()[0]
1102 dirty = old == wcrev or self._wcchanged()[0]
1103 if _updateprompt(self.ui, self, dirty, wcrev, new):
1103 if _updateprompt(self.ui, self, dirty, wcrev, new):
1104 self.get(state, False)
1104 self.get(state, False)
1105
1105
1106 def push(self, opts):
1106 def push(self, opts):
1107 # push is a no-op for SVN
1107 # push is a no-op for SVN
1108 return True
1108 return True
1109
1109
1110 @annotatesubrepoerror
1110 @annotatesubrepoerror
1111 def files(self):
1111 def files(self):
1112 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1112 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1113 doc = xml.dom.minidom.parseString(output)
1113 doc = xml.dom.minidom.parseString(output)
1114 paths = []
1114 paths = []
1115 for e in doc.getElementsByTagName('entry'):
1115 for e in doc.getElementsByTagName('entry'):
1116 kind = str(e.getAttribute('kind'))
1116 kind = str(e.getAttribute('kind'))
1117 if kind != 'file':
1117 if kind != 'file':
1118 continue
1118 continue
1119 name = ''.join(c.data for c
1119 name = ''.join(c.data for c
1120 in e.getElementsByTagName('name')[0].childNodes
1120 in e.getElementsByTagName('name')[0].childNodes
1121 if c.nodeType == c.TEXT_NODE)
1121 if c.nodeType == c.TEXT_NODE)
1122 paths.append(name.encode('utf-8'))
1122 paths.append(name.encode('utf-8'))
1123 return paths
1123 return paths
1124
1124
1125 def filedata(self, name):
1125 def filedata(self, name):
1126 return self._svncommand(['cat'], name)[0]
1126 return self._svncommand(['cat'], name)[0]
1127
1127
1128
1128
1129 class gitsubrepo(abstractsubrepo):
1129 class gitsubrepo(abstractsubrepo):
1130 def __init__(self, ctx, path, state):
1130 def __init__(self, ctx, path, state):
1131 super(gitsubrepo, self).__init__(ctx._repo.ui)
1131 super(gitsubrepo, self).__init__(ctx._repo.ui)
1132 self._state = state
1132 self._state = state
1133 self._ctx = ctx
1133 self._ctx = ctx
1134 self._path = path
1134 self._path = path
1135 self._relpath = os.path.join(reporelpath(ctx._repo), path)
1135 self._relpath = os.path.join(reporelpath(ctx._repo), path)
1136 self._abspath = ctx._repo.wjoin(path)
1136 self._abspath = ctx._repo.wjoin(path)
1137 self._subparent = ctx._repo
1137 self._subparent = ctx._repo
1138 self._ensuregit()
1138 self._ensuregit()
1139
1139
1140 def _ensuregit(self):
1140 def _ensuregit(self):
1141 try:
1141 try:
1142 self._gitexecutable = 'git'
1142 self._gitexecutable = 'git'
1143 out, err = self._gitnodir(['--version'])
1143 out, err = self._gitnodir(['--version'])
1144 except OSError, e:
1144 except OSError, e:
1145 if e.errno != 2 or os.name != 'nt':
1145 if e.errno != 2 or os.name != 'nt':
1146 raise
1146 raise
1147 self._gitexecutable = 'git.cmd'
1147 self._gitexecutable = 'git.cmd'
1148 out, err = self._gitnodir(['--version'])
1148 out, err = self._gitnodir(['--version'])
1149 versionstatus = self._checkversion(out)
1149 versionstatus = self._checkversion(out)
1150 if versionstatus == 'unknown':
1150 if versionstatus == 'unknown':
1151 self.ui.warn(_('cannot retrieve git version\n'))
1151 self.ui.warn(_('cannot retrieve git version\n'))
1152 elif versionstatus == 'abort':
1152 elif versionstatus == 'abort':
1153 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1153 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1154 elif versionstatus == 'warning':
1154 elif versionstatus == 'warning':
1155 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1155 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1156
1156
1157 @staticmethod
1157 @staticmethod
1158 def _gitversion(out):
1158 def _gitversion(out):
1159 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1159 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1160 if m:
1160 if m:
1161 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1161 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1162
1162
1163 m = re.search(r'^git version (\d+)\.(\d+)', out)
1163 m = re.search(r'^git version (\d+)\.(\d+)', out)
1164 if m:
1164 if m:
1165 return (int(m.group(1)), int(m.group(2)), 0)
1165 return (int(m.group(1)), int(m.group(2)), 0)
1166
1166
1167 return -1
1167 return -1
1168
1168
1169 @staticmethod
1169 @staticmethod
1170 def _checkversion(out):
1170 def _checkversion(out):
1171 '''ensure git version is new enough
1171 '''ensure git version is new enough
1172
1172
1173 >>> _checkversion = gitsubrepo._checkversion
1173 >>> _checkversion = gitsubrepo._checkversion
1174 >>> _checkversion('git version 1.6.0')
1174 >>> _checkversion('git version 1.6.0')
1175 'ok'
1175 'ok'
1176 >>> _checkversion('git version 1.8.5')
1176 >>> _checkversion('git version 1.8.5')
1177 'ok'
1177 'ok'
1178 >>> _checkversion('git version 1.4.0')
1178 >>> _checkversion('git version 1.4.0')
1179 'abort'
1179 'abort'
1180 >>> _checkversion('git version 1.5.0')
1180 >>> _checkversion('git version 1.5.0')
1181 'warning'
1181 'warning'
1182 >>> _checkversion('git version 1.9-rc0')
1182 >>> _checkversion('git version 1.9-rc0')
1183 'ok'
1183 'ok'
1184 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1184 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1185 'ok'
1185 'ok'
1186 >>> _checkversion('git version 1.9.0.GIT')
1186 >>> _checkversion('git version 1.9.0.GIT')
1187 'ok'
1187 'ok'
1188 >>> _checkversion('git version 12345')
1188 >>> _checkversion('git version 12345')
1189 'unknown'
1189 'unknown'
1190 >>> _checkversion('no')
1190 >>> _checkversion('no')
1191 'unknown'
1191 'unknown'
1192 '''
1192 '''
1193 version = gitsubrepo._gitversion(out)
1193 version = gitsubrepo._gitversion(out)
1194 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1194 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1195 # despite the docstring comment. For now, error on 1.4.0, warn on
1195 # despite the docstring comment. For now, error on 1.4.0, warn on
1196 # 1.5.0 but attempt to continue.
1196 # 1.5.0 but attempt to continue.
1197 if version == -1:
1197 if version == -1:
1198 return 'unknown'
1198 return 'unknown'
1199 if version < (1, 5, 0):
1199 if version < (1, 5, 0):
1200 return 'abort'
1200 return 'abort'
1201 elif version < (1, 6, 0):
1201 elif version < (1, 6, 0):
1202 return 'warning'
1202 return 'warning'
1203 return 'ok'
1203 return 'ok'
1204
1204
1205 def _gitcommand(self, commands, env=None, stream=False):
1205 def _gitcommand(self, commands, env=None, stream=False):
1206 return self._gitdir(commands, env=env, stream=stream)[0]
1206 return self._gitdir(commands, env=env, stream=stream)[0]
1207
1207
1208 def _gitdir(self, commands, env=None, stream=False):
1208 def _gitdir(self, commands, env=None, stream=False):
1209 return self._gitnodir(commands, env=env, stream=stream,
1209 return self._gitnodir(commands, env=env, stream=stream,
1210 cwd=self._abspath)
1210 cwd=self._abspath)
1211
1211
1212 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1212 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1213 """Calls the git command
1213 """Calls the git command
1214
1214
1215 The methods tries to call the git command. versions prior to 1.6.0
1215 The methods tries to call the git command. versions prior to 1.6.0
1216 are not supported and very probably fail.
1216 are not supported and very probably fail.
1217 """
1217 """
1218 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1218 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1219 # unless ui.quiet is set, print git's stderr,
1219 # unless ui.quiet is set, print git's stderr,
1220 # which is mostly progress and useful info
1220 # which is mostly progress and useful info
1221 errpipe = None
1221 errpipe = None
1222 if self.ui.quiet:
1222 if self.ui.quiet:
1223 errpipe = open(os.devnull, 'w')
1223 errpipe = open(os.devnull, 'w')
1224 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1224 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1225 cwd=cwd, env=env, close_fds=util.closefds,
1225 cwd=cwd, env=env, close_fds=util.closefds,
1226 stdout=subprocess.PIPE, stderr=errpipe)
1226 stdout=subprocess.PIPE, stderr=errpipe)
1227 if stream:
1227 if stream:
1228 return p.stdout, None
1228 return p.stdout, None
1229
1229
1230 retdata = p.stdout.read().strip()
1230 retdata = p.stdout.read().strip()
1231 # wait for the child to exit to avoid race condition.
1231 # wait for the child to exit to avoid race condition.
1232 p.wait()
1232 p.wait()
1233
1233
1234 if p.returncode != 0 and p.returncode != 1:
1234 if p.returncode != 0 and p.returncode != 1:
1235 # there are certain error codes that are ok
1235 # there are certain error codes that are ok
1236 command = commands[0]
1236 command = commands[0]
1237 if command in ('cat-file', 'symbolic-ref'):
1237 if command in ('cat-file', 'symbolic-ref'):
1238 return retdata, p.returncode
1238 return retdata, p.returncode
1239 # for all others, abort
1239 # for all others, abort
1240 raise util.Abort('git %s error %d in %s' %
1240 raise util.Abort('git %s error %d in %s' %
1241 (command, p.returncode, self._relpath))
1241 (command, p.returncode, self._relpath))
1242
1242
1243 return retdata, p.returncode
1243 return retdata, p.returncode
1244
1244
1245 def _gitmissing(self):
1245 def _gitmissing(self):
1246 return not os.path.exists(os.path.join(self._abspath, '.git'))
1246 return not os.path.exists(os.path.join(self._abspath, '.git'))
1247
1247
1248 def _gitstate(self):
1248 def _gitstate(self):
1249 return self._gitcommand(['rev-parse', 'HEAD'])
1249 return self._gitcommand(['rev-parse', 'HEAD'])
1250
1250
1251 def _gitcurrentbranch(self):
1251 def _gitcurrentbranch(self):
1252 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1252 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1253 if err:
1253 if err:
1254 current = None
1254 current = None
1255 return current
1255 return current
1256
1256
1257 def _gitremote(self, remote):
1257 def _gitremote(self, remote):
1258 out = self._gitcommand(['remote', 'show', '-n', remote])
1258 out = self._gitcommand(['remote', 'show', '-n', remote])
1259 line = out.split('\n')[1]
1259 line = out.split('\n')[1]
1260 i = line.index('URL: ') + len('URL: ')
1260 i = line.index('URL: ') + len('URL: ')
1261 return line[i:]
1261 return line[i:]
1262
1262
1263 def _githavelocally(self, revision):
1263 def _githavelocally(self, revision):
1264 out, code = self._gitdir(['cat-file', '-e', revision])
1264 out, code = self._gitdir(['cat-file', '-e', revision])
1265 return code == 0
1265 return code == 0
1266
1266
1267 def _gitisancestor(self, r1, r2):
1267 def _gitisancestor(self, r1, r2):
1268 base = self._gitcommand(['merge-base', r1, r2])
1268 base = self._gitcommand(['merge-base', r1, r2])
1269 return base == r1
1269 return base == r1
1270
1270
1271 def _gitisbare(self):
1271 def _gitisbare(self):
1272 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1272 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1273
1273
1274 def _gitupdatestat(self):
1274 def _gitupdatestat(self):
1275 """This must be run before git diff-index.
1275 """This must be run before git diff-index.
1276 diff-index only looks at changes to file stat;
1276 diff-index only looks at changes to file stat;
1277 this command looks at file contents and updates the stat."""
1277 this command looks at file contents and updates the stat."""
1278 self._gitcommand(['update-index', '-q', '--refresh'])
1278 self._gitcommand(['update-index', '-q', '--refresh'])
1279
1279
1280 def _gitbranchmap(self):
1280 def _gitbranchmap(self):
1281 '''returns 2 things:
1281 '''returns 2 things:
1282 a map from git branch to revision
1282 a map from git branch to revision
1283 a map from revision to branches'''
1283 a map from revision to branches'''
1284 branch2rev = {}
1284 branch2rev = {}
1285 rev2branch = {}
1285 rev2branch = {}
1286
1286
1287 out = self._gitcommand(['for-each-ref', '--format',
1287 out = self._gitcommand(['for-each-ref', '--format',
1288 '%(objectname) %(refname)'])
1288 '%(objectname) %(refname)'])
1289 for line in out.split('\n'):
1289 for line in out.split('\n'):
1290 revision, ref = line.split(' ')
1290 revision, ref = line.split(' ')
1291 if (not ref.startswith('refs/heads/') and
1291 if (not ref.startswith('refs/heads/') and
1292 not ref.startswith('refs/remotes/')):
1292 not ref.startswith('refs/remotes/')):
1293 continue
1293 continue
1294 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1294 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1295 continue # ignore remote/HEAD redirects
1295 continue # ignore remote/HEAD redirects
1296 branch2rev[ref] = revision
1296 branch2rev[ref] = revision
1297 rev2branch.setdefault(revision, []).append(ref)
1297 rev2branch.setdefault(revision, []).append(ref)
1298 return branch2rev, rev2branch
1298 return branch2rev, rev2branch
1299
1299
1300 def _gittracking(self, branches):
1300 def _gittracking(self, branches):
1301 'return map of remote branch to local tracking branch'
1301 'return map of remote branch to local tracking branch'
1302 # assumes no more than one local tracking branch for each remote
1302 # assumes no more than one local tracking branch for each remote
1303 tracking = {}
1303 tracking = {}
1304 for b in branches:
1304 for b in branches:
1305 if b.startswith('refs/remotes/'):
1305 if b.startswith('refs/remotes/'):
1306 continue
1306 continue
1307 bname = b.split('/', 2)[2]
1307 bname = b.split('/', 2)[2]
1308 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1308 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1309 if remote:
1309 if remote:
1310 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1310 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1311 tracking['refs/remotes/%s/%s' %
1311 tracking['refs/remotes/%s/%s' %
1312 (remote, ref.split('/', 2)[2])] = b
1312 (remote, ref.split('/', 2)[2])] = b
1313 return tracking
1313 return tracking
1314
1314
1315 def _abssource(self, source):
1315 def _abssource(self, source):
1316 if '://' not in source:
1316 if '://' not in source:
1317 # recognize the scp syntax as an absolute source
1317 # recognize the scp syntax as an absolute source
1318 colon = source.find(':')
1318 colon = source.find(':')
1319 if colon != -1 and '/' not in source[:colon]:
1319 if colon != -1 and '/' not in source[:colon]:
1320 return source
1320 return source
1321 self._subsource = source
1321 self._subsource = source
1322 return _abssource(self)
1322 return _abssource(self)
1323
1323
1324 def _fetch(self, source, revision):
1324 def _fetch(self, source, revision):
1325 if self._gitmissing():
1325 if self._gitmissing():
1326 source = self._abssource(source)
1326 source = self._abssource(source)
1327 self.ui.status(_('cloning subrepo %s from %s\n') %
1327 self.ui.status(_('cloning subrepo %s from %s\n') %
1328 (self._relpath, source))
1328 (self._relpath, source))
1329 self._gitnodir(['clone', source, self._abspath])
1329 self._gitnodir(['clone', source, self._abspath])
1330 if self._githavelocally(revision):
1330 if self._githavelocally(revision):
1331 return
1331 return
1332 self.ui.status(_('pulling subrepo %s from %s\n') %
1332 self.ui.status(_('pulling subrepo %s from %s\n') %
1333 (self._relpath, self._gitremote('origin')))
1333 (self._relpath, self._gitremote('origin')))
1334 # try only origin: the originally cloned repo
1334 # try only origin: the originally cloned repo
1335 self._gitcommand(['fetch'])
1335 self._gitcommand(['fetch'])
1336 if not self._githavelocally(revision):
1336 if not self._githavelocally(revision):
1337 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1337 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1338 (revision, self._relpath))
1338 (revision, self._relpath))
1339
1339
1340 @annotatesubrepoerror
1340 @annotatesubrepoerror
1341 def dirty(self, ignoreupdate=False):
1341 def dirty(self, ignoreupdate=False):
1342 if self._gitmissing():
1342 if self._gitmissing():
1343 return self._state[1] != ''
1343 return self._state[1] != ''
1344 if self._gitisbare():
1344 if self._gitisbare():
1345 return True
1345 return True
1346 if not ignoreupdate and self._state[1] != self._gitstate():
1346 if not ignoreupdate and self._state[1] != self._gitstate():
1347 # different version checked out
1347 # different version checked out
1348 return True
1348 return True
1349 # check for staged changes or modified files; ignore untracked files
1349 # check for staged changes or modified files; ignore untracked files
1350 self._gitupdatestat()
1350 self._gitupdatestat()
1351 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1351 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1352 return code == 1
1352 return code == 1
1353
1353
1354 def basestate(self):
1354 def basestate(self):
1355 return self._gitstate()
1355 return self._gitstate()
1356
1356
1357 @annotatesubrepoerror
1357 @annotatesubrepoerror
1358 def get(self, state, overwrite=False):
1358 def get(self, state, overwrite=False):
1359 source, revision, kind = state
1359 source, revision, kind = state
1360 if not revision:
1360 if not revision:
1361 self.remove()
1361 self.remove()
1362 return
1362 return
1363 self._fetch(source, revision)
1363 self._fetch(source, revision)
1364 # if the repo was set to be bare, unbare it
1364 # if the repo was set to be bare, unbare it
1365 if self._gitisbare():
1365 if self._gitisbare():
1366 self._gitcommand(['config', 'core.bare', 'false'])
1366 self._gitcommand(['config', 'core.bare', 'false'])
1367 if self._gitstate() == revision:
1367 if self._gitstate() == revision:
1368 self._gitcommand(['reset', '--hard', 'HEAD'])
1368 self._gitcommand(['reset', '--hard', 'HEAD'])
1369 return
1369 return
1370 elif self._gitstate() == revision:
1370 elif self._gitstate() == revision:
1371 if overwrite:
1371 if overwrite:
1372 # first reset the index to unmark new files for commit, because
1372 # first reset the index to unmark new files for commit, because
1373 # reset --hard will otherwise throw away files added for commit,
1373 # reset --hard will otherwise throw away files added for commit,
1374 # not just unmark them.
1374 # not just unmark them.
1375 self._gitcommand(['reset', 'HEAD'])
1375 self._gitcommand(['reset', 'HEAD'])
1376 self._gitcommand(['reset', '--hard', 'HEAD'])
1376 self._gitcommand(['reset', '--hard', 'HEAD'])
1377 return
1377 return
1378 branch2rev, rev2branch = self._gitbranchmap()
1378 branch2rev, rev2branch = self._gitbranchmap()
1379
1379
1380 def checkout(args):
1380 def checkout(args):
1381 cmd = ['checkout']
1381 cmd = ['checkout']
1382 if overwrite:
1382 if overwrite:
1383 # first reset the index to unmark new files for commit, because
1383 # first reset the index to unmark new files for commit, because
1384 # the -f option will otherwise throw away files added for
1384 # the -f option will otherwise throw away files added for
1385 # commit, not just unmark them.
1385 # commit, not just unmark them.
1386 self._gitcommand(['reset', 'HEAD'])
1386 self._gitcommand(['reset', 'HEAD'])
1387 cmd.append('-f')
1387 cmd.append('-f')
1388 self._gitcommand(cmd + args)
1388 self._gitcommand(cmd + args)
1389 _sanitize(self.ui, self._abspath, '.git')
1389 _sanitize(self.ui, self._abspath, '.git')
1390
1390
1391 def rawcheckout():
1391 def rawcheckout():
1392 # no branch to checkout, check it out with no branch
1392 # no branch to checkout, check it out with no branch
1393 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1393 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1394 self._relpath)
1394 self._relpath)
1395 self.ui.warn(_('check out a git branch if you intend '
1395 self.ui.warn(_('check out a git branch if you intend '
1396 'to make changes\n'))
1396 'to make changes\n'))
1397 checkout(['-q', revision])
1397 checkout(['-q', revision])
1398
1398
1399 if revision not in rev2branch:
1399 if revision not in rev2branch:
1400 rawcheckout()
1400 rawcheckout()
1401 return
1401 return
1402 branches = rev2branch[revision]
1402 branches = rev2branch[revision]
1403 firstlocalbranch = None
1403 firstlocalbranch = None
1404 for b in branches:
1404 for b in branches:
1405 if b == 'refs/heads/master':
1405 if b == 'refs/heads/master':
1406 # master trumps all other branches
1406 # master trumps all other branches
1407 checkout(['refs/heads/master'])
1407 checkout(['refs/heads/master'])
1408 return
1408 return
1409 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1409 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1410 firstlocalbranch = b
1410 firstlocalbranch = b
1411 if firstlocalbranch:
1411 if firstlocalbranch:
1412 checkout([firstlocalbranch])
1412 checkout([firstlocalbranch])
1413 return
1413 return
1414
1414
1415 tracking = self._gittracking(branch2rev.keys())
1415 tracking = self._gittracking(branch2rev.keys())
1416 # choose a remote branch already tracked if possible
1416 # choose a remote branch already tracked if possible
1417 remote = branches[0]
1417 remote = branches[0]
1418 if remote not in tracking:
1418 if remote not in tracking:
1419 for b in branches:
1419 for b in branches:
1420 if b in tracking:
1420 if b in tracking:
1421 remote = b
1421 remote = b
1422 break
1422 break
1423
1423
1424 if remote not in tracking:
1424 if remote not in tracking:
1425 # create a new local tracking branch
1425 # create a new local tracking branch
1426 local = remote.split('/', 3)[3]
1426 local = remote.split('/', 3)[3]
1427 checkout(['-b', local, remote])
1427 checkout(['-b', local, remote])
1428 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1428 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1429 # When updating to a tracked remote branch,
1429 # When updating to a tracked remote branch,
1430 # if the local tracking branch is downstream of it,
1430 # if the local tracking branch is downstream of it,
1431 # a normal `git pull` would have performed a "fast-forward merge"
1431 # a normal `git pull` would have performed a "fast-forward merge"
1432 # which is equivalent to updating the local branch to the remote.
1432 # which is equivalent to updating the local branch to the remote.
1433 # Since we are only looking at branching at update, we need to
1433 # Since we are only looking at branching at update, we need to
1434 # detect this situation and perform this action lazily.
1434 # detect this situation and perform this action lazily.
1435 if tracking[remote] != self._gitcurrentbranch():
1435 if tracking[remote] != self._gitcurrentbranch():
1436 checkout([tracking[remote]])
1436 checkout([tracking[remote]])
1437 self._gitcommand(['merge', '--ff', remote])
1437 self._gitcommand(['merge', '--ff', remote])
1438 _sanitize(self.ui, self._abspath, '.git')
1438 _sanitize(self.ui, self._abspath, '.git')
1439 else:
1439 else:
1440 # a real merge would be required, just checkout the revision
1440 # a real merge would be required, just checkout the revision
1441 rawcheckout()
1441 rawcheckout()
1442
1442
1443 @annotatesubrepoerror
1443 @annotatesubrepoerror
1444 def commit(self, text, user, date):
1444 def commit(self, text, user, date):
1445 if self._gitmissing():
1445 if self._gitmissing():
1446 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1446 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1447 cmd = ['commit', '-a', '-m', text]
1447 cmd = ['commit', '-a', '-m', text]
1448 env = os.environ.copy()
1448 env = os.environ.copy()
1449 if user:
1449 if user:
1450 cmd += ['--author', user]
1450 cmd += ['--author', user]
1451 if date:
1451 if date:
1452 # git's date parser silently ignores when seconds < 1e9
1452 # git's date parser silently ignores when seconds < 1e9
1453 # convert to ISO8601
1453 # convert to ISO8601
1454 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1454 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1455 '%Y-%m-%dT%H:%M:%S %1%2')
1455 '%Y-%m-%dT%H:%M:%S %1%2')
1456 self._gitcommand(cmd, env=env)
1456 self._gitcommand(cmd, env=env)
1457 # make sure commit works otherwise HEAD might not exist under certain
1457 # make sure commit works otherwise HEAD might not exist under certain
1458 # circumstances
1458 # circumstances
1459 return self._gitstate()
1459 return self._gitstate()
1460
1460
1461 @annotatesubrepoerror
1461 @annotatesubrepoerror
1462 def merge(self, state):
1462 def merge(self, state):
1463 source, revision, kind = state
1463 source, revision, kind = state
1464 self._fetch(source, revision)
1464 self._fetch(source, revision)
1465 base = self._gitcommand(['merge-base', revision, self._state[1]])
1465 base = self._gitcommand(['merge-base', revision, self._state[1]])
1466 self._gitupdatestat()
1466 self._gitupdatestat()
1467 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1467 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1468
1468
1469 def mergefunc():
1469 def mergefunc():
1470 if base == revision:
1470 if base == revision:
1471 self.get(state) # fast forward merge
1471 self.get(state) # fast forward merge
1472 elif base != self._state[1]:
1472 elif base != self._state[1]:
1473 self._gitcommand(['merge', '--no-commit', revision])
1473 self._gitcommand(['merge', '--no-commit', revision])
1474 _sanitize(self.ui, self._abspath, '.git')
1474 _sanitize(self.ui, self._abspath, '.git')
1475
1475
1476 if self.dirty():
1476 if self.dirty():
1477 if self._gitstate() != revision:
1477 if self._gitstate() != revision:
1478 dirty = self._gitstate() == self._state[1] or code != 0
1478 dirty = self._gitstate() == self._state[1] or code != 0
1479 if _updateprompt(self.ui, self, dirty,
1479 if _updateprompt(self.ui, self, dirty,
1480 self._state[1][:7], revision[:7]):
1480 self._state[1][:7], revision[:7]):
1481 mergefunc()
1481 mergefunc()
1482 else:
1482 else:
1483 mergefunc()
1483 mergefunc()
1484
1484
1485 @annotatesubrepoerror
1485 @annotatesubrepoerror
1486 def push(self, opts):
1486 def push(self, opts):
1487 force = opts.get('force')
1487 force = opts.get('force')
1488
1488
1489 if not self._state[1]:
1489 if not self._state[1]:
1490 return True
1490 return True
1491 if self._gitmissing():
1491 if self._gitmissing():
1492 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1492 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1493 # if a branch in origin contains the revision, nothing to do
1493 # if a branch in origin contains the revision, nothing to do
1494 branch2rev, rev2branch = self._gitbranchmap()
1494 branch2rev, rev2branch = self._gitbranchmap()
1495 if self._state[1] in rev2branch:
1495 if self._state[1] in rev2branch:
1496 for b in rev2branch[self._state[1]]:
1496 for b in rev2branch[self._state[1]]:
1497 if b.startswith('refs/remotes/origin/'):
1497 if b.startswith('refs/remotes/origin/'):
1498 return True
1498 return True
1499 for b, revision in branch2rev.iteritems():
1499 for b, revision in branch2rev.iteritems():
1500 if b.startswith('refs/remotes/origin/'):
1500 if b.startswith('refs/remotes/origin/'):
1501 if self._gitisancestor(self._state[1], revision):
1501 if self._gitisancestor(self._state[1], revision):
1502 return True
1502 return True
1503 # otherwise, try to push the currently checked out branch
1503 # otherwise, try to push the currently checked out branch
1504 cmd = ['push']
1504 cmd = ['push']
1505 if force:
1505 if force:
1506 cmd.append('--force')
1506 cmd.append('--force')
1507
1507
1508 current = self._gitcurrentbranch()
1508 current = self._gitcurrentbranch()
1509 if current:
1509 if current:
1510 # determine if the current branch is even useful
1510 # determine if the current branch is even useful
1511 if not self._gitisancestor(self._state[1], current):
1511 if not self._gitisancestor(self._state[1], current):
1512 self.ui.warn(_('unrelated git branch checked out '
1512 self.ui.warn(_('unrelated git branch checked out '
1513 'in subrepo %s\n') % self._relpath)
1513 'in subrepo %s\n') % self._relpath)
1514 return False
1514 return False
1515 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1515 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1516 (current.split('/', 2)[2], self._relpath))
1516 (current.split('/', 2)[2], self._relpath))
1517 ret = self._gitdir(cmd + ['origin', current])
1517 ret = self._gitdir(cmd + ['origin', current])
1518 return ret[1] == 0
1518 return ret[1] == 0
1519 else:
1519 else:
1520 self.ui.warn(_('no branch checked out in subrepo %s\n'
1520 self.ui.warn(_('no branch checked out in subrepo %s\n'
1521 'cannot push revision %s\n') %
1521 'cannot push revision %s\n') %
1522 (self._relpath, self._state[1]))
1522 (self._relpath, self._state[1]))
1523 return False
1523 return False
1524
1524
1525 @annotatesubrepoerror
1525 @annotatesubrepoerror
1526 def add(self, ui, match, prefix, explicitonly, **opts):
1527 if self._gitmissing():
1528 return []
1529 if match.files():
1530 files = match.files()
1531 else:
1532 (modified, added, removed,
1533 deleted, unknown, ignored, clean) = self.status(None)
1534 files = unknown
1535
1536 files = [f for f in files if match(f)]
1537 for f in files:
1538 exact = match.exact(f)
1539 command = ["add"]
1540 if exact:
1541 command.append("-f") #should be added, even if ignored
1542 if ui.verbose or not exact:
1543 ui.status(_('adding %s\n') % match.rel(f))
1544 if not opts.get('dry_run'):
1545 self._gitcommand(command + [f])
1546 return []
1547
1548 @annotatesubrepoerror
1526 def remove(self):
1549 def remove(self):
1527 if self._gitmissing():
1550 if self._gitmissing():
1528 return
1551 return
1529 if self.dirty():
1552 if self.dirty():
1530 self.ui.warn(_('not removing repo %s because '
1553 self.ui.warn(_('not removing repo %s because '
1531 'it has changes.\n') % self._relpath)
1554 'it has changes.\n') % self._relpath)
1532 return
1555 return
1533 # we can't fully delete the repository as it may contain
1556 # we can't fully delete the repository as it may contain
1534 # local-only history
1557 # local-only history
1535 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1558 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1536 self._gitcommand(['config', 'core.bare', 'true'])
1559 self._gitcommand(['config', 'core.bare', 'true'])
1537 for f in os.listdir(self._abspath):
1560 for f in os.listdir(self._abspath):
1538 if f == '.git':
1561 if f == '.git':
1539 continue
1562 continue
1540 path = os.path.join(self._abspath, f)
1563 path = os.path.join(self._abspath, f)
1541 if os.path.isdir(path) and not os.path.islink(path):
1564 if os.path.isdir(path) and not os.path.islink(path):
1542 shutil.rmtree(path)
1565 shutil.rmtree(path)
1543 else:
1566 else:
1544 os.remove(path)
1567 os.remove(path)
1545
1568
1546 def archive(self, archiver, prefix, match=None):
1569 def archive(self, archiver, prefix, match=None):
1547 total = 0
1570 total = 0
1548 source, revision = self._state
1571 source, revision = self._state
1549 if not revision:
1572 if not revision:
1550 return total
1573 return total
1551 self._fetch(source, revision)
1574 self._fetch(source, revision)
1552
1575
1553 # Parse git's native archive command.
1576 # Parse git's native archive command.
1554 # This should be much faster than manually traversing the trees
1577 # This should be much faster than manually traversing the trees
1555 # and objects with many subprocess calls.
1578 # and objects with many subprocess calls.
1556 tarstream = self._gitcommand(['archive', revision], stream=True)
1579 tarstream = self._gitcommand(['archive', revision], stream=True)
1557 tar = tarfile.open(fileobj=tarstream, mode='r|')
1580 tar = tarfile.open(fileobj=tarstream, mode='r|')
1558 relpath = subrelpath(self)
1581 relpath = subrelpath(self)
1559 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1582 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1560 for i, info in enumerate(tar):
1583 for i, info in enumerate(tar):
1561 if info.isdir():
1584 if info.isdir():
1562 continue
1585 continue
1563 if match and not match(info.name):
1586 if match and not match(info.name):
1564 continue
1587 continue
1565 if info.issym():
1588 if info.issym():
1566 data = info.linkname
1589 data = info.linkname
1567 else:
1590 else:
1568 data = tar.extractfile(info).read()
1591 data = tar.extractfile(info).read()
1569 archiver.addfile(os.path.join(prefix, self._path, info.name),
1592 archiver.addfile(os.path.join(prefix, self._path, info.name),
1570 info.mode, info.issym(), data)
1593 info.mode, info.issym(), data)
1571 total += 1
1594 total += 1
1572 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1595 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1573 unit=_('files'))
1596 unit=_('files'))
1574 self.ui.progress(_('archiving (%s)') % relpath, None)
1597 self.ui.progress(_('archiving (%s)') % relpath, None)
1575 return total
1598 return total
1576
1599
1577
1600
1578 @annotatesubrepoerror
1601 @annotatesubrepoerror
1579 def cat(self, match, prefix, **opts):
1602 def cat(self, match, prefix, **opts):
1580 rev = self._state[1]
1603 rev = self._state[1]
1581 if match.anypats():
1604 if match.anypats():
1582 return 1 #No support for include/exclude yet
1605 return 1 #No support for include/exclude yet
1583
1606
1584 if not match.files():
1607 if not match.files():
1585 return 1
1608 return 1
1586
1609
1587 for f in match.files():
1610 for f in match.files():
1588 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1611 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1589 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1612 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1590 self._ctx.node(),
1613 self._ctx.node(),
1591 pathname=os.path.join(prefix, f))
1614 pathname=os.path.join(prefix, f))
1592 fp.write(output)
1615 fp.write(output)
1593 fp.close()
1616 fp.close()
1594 return 0
1617 return 0
1595
1618
1596
1619
1597 @annotatesubrepoerror
1620 @annotatesubrepoerror
1598 def status(self, rev2, **opts):
1621 def status(self, rev2, **opts):
1599 rev1 = self._state[1]
1622 rev1 = self._state[1]
1600 if self._gitmissing() or not rev1:
1623 if self._gitmissing() or not rev1:
1601 # if the repo is missing, return no results
1624 # if the repo is missing, return no results
1602 return [], [], [], [], [], [], []
1625 return [], [], [], [], [], [], []
1603 modified, added, removed = [], [], []
1626 modified, added, removed = [], [], []
1604 self._gitupdatestat()
1627 self._gitupdatestat()
1605 if rev2:
1628 if rev2:
1606 command = ['diff-tree', rev1, rev2]
1629 command = ['diff-tree', rev1, rev2]
1607 else:
1630 else:
1608 command = ['diff-index', rev1]
1631 command = ['diff-index', rev1]
1609 out = self._gitcommand(command)
1632 out = self._gitcommand(command)
1610 for line in out.split('\n'):
1633 for line in out.split('\n'):
1611 tab = line.find('\t')
1634 tab = line.find('\t')
1612 if tab == -1:
1635 if tab == -1:
1613 continue
1636 continue
1614 status, f = line[tab - 1], line[tab + 1:]
1637 status, f = line[tab - 1], line[tab + 1:]
1615 if status == 'M':
1638 if status == 'M':
1616 modified.append(f)
1639 modified.append(f)
1617 elif status == 'A':
1640 elif status == 'A':
1618 added.append(f)
1641 added.append(f)
1619 elif status == 'D':
1642 elif status == 'D':
1620 removed.append(f)
1643 removed.append(f)
1621
1644
1622 deleted, unknown, ignored, clean = [], [], [], []
1645 deleted, unknown, ignored, clean = [], [], [], []
1623
1646
1624 if not rev2:
1647 if not rev2:
1625 command = ['ls-files', '--others', '--exclude-standard']
1648 command = ['ls-files', '--others', '--exclude-standard']
1626 out = self._gitcommand(command)
1649 out = self._gitcommand(command)
1627 for line in out.split('\n'):
1650 for line in out.split('\n'):
1628 if len(line) == 0:
1651 if len(line) == 0:
1629 continue
1652 continue
1630 unknown.append(line)
1653 unknown.append(line)
1631
1654
1632 return scmutil.status(modified, added, removed, deleted,
1655 return scmutil.status(modified, added, removed, deleted,
1633 unknown, ignored, clean)
1656 unknown, ignored, clean)
1634
1657
1635 @annotatesubrepoerror
1658 @annotatesubrepoerror
1636 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1659 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1637 node1 = self._state[1]
1660 node1 = self._state[1]
1638 cmd = ['diff']
1661 cmd = ['diff']
1639 if opts['stat']:
1662 if opts['stat']:
1640 cmd.append('--stat')
1663 cmd.append('--stat')
1641 else:
1664 else:
1642 # for Git, this also implies '-p'
1665 # for Git, this also implies '-p'
1643 cmd.append('-U%d' % diffopts.context)
1666 cmd.append('-U%d' % diffopts.context)
1644
1667
1645 gitprefix = os.path.join(prefix, self._path)
1668 gitprefix = os.path.join(prefix, self._path)
1646
1669
1647 if diffopts.noprefix:
1670 if diffopts.noprefix:
1648 cmd.extend(['--src-prefix=%s/' % gitprefix,
1671 cmd.extend(['--src-prefix=%s/' % gitprefix,
1649 '--dst-prefix=%s/' % gitprefix])
1672 '--dst-prefix=%s/' % gitprefix])
1650 else:
1673 else:
1651 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1674 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1652 '--dst-prefix=b/%s/' % gitprefix])
1675 '--dst-prefix=b/%s/' % gitprefix])
1653
1676
1654 if diffopts.ignorews:
1677 if diffopts.ignorews:
1655 cmd.append('--ignore-all-space')
1678 cmd.append('--ignore-all-space')
1656 if diffopts.ignorewsamount:
1679 if diffopts.ignorewsamount:
1657 cmd.append('--ignore-space-change')
1680 cmd.append('--ignore-space-change')
1658 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1681 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1659 and diffopts.ignoreblanklines:
1682 and diffopts.ignoreblanklines:
1660 cmd.append('--ignore-blank-lines')
1683 cmd.append('--ignore-blank-lines')
1661
1684
1662 cmd.append(node1)
1685 cmd.append(node1)
1663 if node2:
1686 if node2:
1664 cmd.append(node2)
1687 cmd.append(node2)
1665
1688
1666 if match.anypats():
1689 if match.anypats():
1667 return #No support for include/exclude yet
1690 return #No support for include/exclude yet
1668
1691
1669 output = ""
1692 output = ""
1670 if match.always():
1693 if match.always():
1671 output += self._gitcommand(cmd) + '\n'
1694 output += self._gitcommand(cmd) + '\n'
1672 elif match.files():
1695 elif match.files():
1673 for f in match.files():
1696 for f in match.files():
1674 output += self._gitcommand(cmd + [f]) + '\n'
1697 output += self._gitcommand(cmd + [f]) + '\n'
1675 elif match(gitprefix): #Subrepo is matched
1698 elif match(gitprefix): #Subrepo is matched
1676 output += self._gitcommand(cmd) + '\n'
1699 output += self._gitcommand(cmd) + '\n'
1677
1700
1678 if output.strip():
1701 if output.strip():
1679 ui.write(output)
1702 ui.write(output)
1680
1703
1681 @annotatesubrepoerror
1704 @annotatesubrepoerror
1682 def revert(self, substate, *pats, **opts):
1705 def revert(self, substate, *pats, **opts):
1683 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1706 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1684 if not opts.get('no_backup'):
1707 if not opts.get('no_backup'):
1685 status = self.status(None)
1708 status = self.status(None)
1686 names = status.modified
1709 names = status.modified
1687 for name in names:
1710 for name in names:
1688 bakname = "%s.orig" % name
1711 bakname = "%s.orig" % name
1689 self.ui.note(_('saving current version of %s as %s\n') %
1712 self.ui.note(_('saving current version of %s as %s\n') %
1690 (name, bakname))
1713 (name, bakname))
1691 util.rename(os.path.join(self._abspath, name),
1714 util.rename(os.path.join(self._abspath, name),
1692 os.path.join(self._abspath, bakname))
1715 os.path.join(self._abspath, bakname))
1693
1716
1694 if not opts.get('dry_run'):
1717 if not opts.get('dry_run'):
1695 self.get(substate, overwrite=True)
1718 self.get(substate, overwrite=True)
1696 return []
1719 return []
1697
1720
1698 def shortid(self, revid):
1721 def shortid(self, revid):
1699 return revid[:7]
1722 return revid[:7]
1700
1723
1701 types = {
1724 types = {
1702 'hg': hgsubrepo,
1725 'hg': hgsubrepo,
1703 'svn': svnsubrepo,
1726 'svn': svnsubrepo,
1704 'git': gitsubrepo,
1727 'git': gitsubrepo,
1705 }
1728 }
@@ -1,853 +1,980 b''
1 #require git
1 #require git
2
2
3 make git commits repeatable
3 make git commits repeatable
4
4
5 $ echo "[core]" >> $HOME/.gitconfig
5 $ echo "[core]" >> $HOME/.gitconfig
6 $ echo "autocrlf = false" >> $HOME/.gitconfig
6 $ echo "autocrlf = false" >> $HOME/.gitconfig
7 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
7 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
8 $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
8 $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
9 $ GIT_AUTHOR_DATE='1234567891 +0000'; export GIT_AUTHOR_DATE
9 $ GIT_AUTHOR_DATE='1234567891 +0000'; export GIT_AUTHOR_DATE
10 $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
10 $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
11 $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
11 $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
12 $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
12 $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
13 $ GIT_CONFIG_NOSYSTEM=1; export GIT_CONFIG_NOSYSTEM
13 $ GIT_CONFIG_NOSYSTEM=1; export GIT_CONFIG_NOSYSTEM
14
14
15 root hg repo
15 root hg repo
16
16
17 $ hg init t
17 $ hg init t
18 $ cd t
18 $ cd t
19 $ echo a > a
19 $ echo a > a
20 $ hg add a
20 $ hg add a
21 $ hg commit -m a
21 $ hg commit -m a
22 $ cd ..
22 $ cd ..
23
23
24 new external git repo
24 new external git repo
25
25
26 $ mkdir gitroot
26 $ mkdir gitroot
27 $ cd gitroot
27 $ cd gitroot
28 $ git init -q
28 $ git init -q
29 $ echo g > g
29 $ echo g > g
30 $ git add g
30 $ git add g
31 $ git commit -q -m g
31 $ git commit -q -m g
32
32
33 add subrepo clone
33 add subrepo clone
34
34
35 $ cd ../t
35 $ cd ../t
36 $ echo 's = [git]../gitroot' > .hgsub
36 $ echo 's = [git]../gitroot' > .hgsub
37 $ git clone -q ../gitroot s
37 $ git clone -q ../gitroot s
38 $ hg add .hgsub
38 $ hg add .hgsub
39 $ hg commit -m 'new git subrepo'
39 $ hg commit -m 'new git subrepo'
40 $ hg debugsub
40 $ hg debugsub
41 path s
41 path s
42 source ../gitroot
42 source ../gitroot
43 revision da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
43 revision da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
44
44
45 record a new commit from upstream from a different branch
45 record a new commit from upstream from a different branch
46
46
47 $ cd ../gitroot
47 $ cd ../gitroot
48 $ git checkout -q -b testing
48 $ git checkout -q -b testing
49 $ echo gg >> g
49 $ echo gg >> g
50 $ git commit -q -a -m gg
50 $ git commit -q -a -m gg
51
51
52 $ cd ../t/s
52 $ cd ../t/s
53 $ git pull -q >/dev/null 2>/dev/null
53 $ git pull -q >/dev/null 2>/dev/null
54 $ git checkout -q -b testing origin/testing >/dev/null
54 $ git checkout -q -b testing origin/testing >/dev/null
55
55
56 $ cd ..
56 $ cd ..
57 $ hg status --subrepos
57 $ hg status --subrepos
58 M s/g
58 M s/g
59 $ hg commit -m 'update git subrepo'
59 $ hg commit -m 'update git subrepo'
60 $ hg debugsub
60 $ hg debugsub
61 path s
61 path s
62 source ../gitroot
62 source ../gitroot
63 revision 126f2a14290cd5ce061fdedc430170e8d39e1c5a
63 revision 126f2a14290cd5ce061fdedc430170e8d39e1c5a
64
64
65 make $GITROOT pushable, by replacing it with a clone with nothing checked out
65 make $GITROOT pushable, by replacing it with a clone with nothing checked out
66
66
67 $ cd ..
67 $ cd ..
68 $ git clone gitroot gitrootbare --bare -q
68 $ git clone gitroot gitrootbare --bare -q
69 $ rm -rf gitroot
69 $ rm -rf gitroot
70 $ mv gitrootbare gitroot
70 $ mv gitrootbare gitroot
71
71
72 clone root
72 clone root
73
73
74 $ cd t
74 $ cd t
75 $ hg clone . ../tc 2> /dev/null
75 $ hg clone . ../tc 2> /dev/null
76 updating to branch default
76 updating to branch default
77 cloning subrepo s from $TESTTMP/gitroot
77 cloning subrepo s from $TESTTMP/gitroot
78 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 $ cd ../tc
79 $ cd ../tc
80 $ hg debugsub
80 $ hg debugsub
81 path s
81 path s
82 source ../gitroot
82 source ../gitroot
83 revision 126f2a14290cd5ce061fdedc430170e8d39e1c5a
83 revision 126f2a14290cd5ce061fdedc430170e8d39e1c5a
84
84
85 update to previous substate
85 update to previous substate
86
86
87 $ hg update 1 -q
87 $ hg update 1 -q
88 $ cat s/g
88 $ cat s/g
89 g
89 g
90 $ hg debugsub
90 $ hg debugsub
91 path s
91 path s
92 source ../gitroot
92 source ../gitroot
93 revision da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
93 revision da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
94
94
95 clone root, make local change
95 clone root, make local change
96
96
97 $ cd ../t
97 $ cd ../t
98 $ hg clone . ../ta 2> /dev/null
98 $ hg clone . ../ta 2> /dev/null
99 updating to branch default
99 updating to branch default
100 cloning subrepo s from $TESTTMP/gitroot
100 cloning subrepo s from $TESTTMP/gitroot
101 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
101 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
102
102
103 $ cd ../ta
103 $ cd ../ta
104 $ echo ggg >> s/g
104 $ echo ggg >> s/g
105 $ hg status --subrepos
105 $ hg status --subrepos
106 M s/g
106 M s/g
107 $ hg diff --subrepos
107 $ hg diff --subrepos
108 diff --git a/s/g b/s/g
108 diff --git a/s/g b/s/g
109 index 089258f..85341ee 100644
109 index 089258f..85341ee 100644
110 --- a/s/g
110 --- a/s/g
111 +++ b/s/g
111 +++ b/s/g
112 @@ -1,2 +1,3 @@
112 @@ -1,2 +1,3 @@
113 g
113 g
114 gg
114 gg
115 +ggg
115 +ggg
116 $ hg commit --subrepos -m ggg
116 $ hg commit --subrepos -m ggg
117 committing subrepository s
117 committing subrepository s
118 $ hg debugsub
118 $ hg debugsub
119 path s
119 path s
120 source ../gitroot
120 source ../gitroot
121 revision 79695940086840c99328513acbe35f90fcd55e57
121 revision 79695940086840c99328513acbe35f90fcd55e57
122
122
123 clone root separately, make different local change
123 clone root separately, make different local change
124
124
125 $ cd ../t
125 $ cd ../t
126 $ hg clone . ../tb 2> /dev/null
126 $ hg clone . ../tb 2> /dev/null
127 updating to branch default
127 updating to branch default
128 cloning subrepo s from $TESTTMP/gitroot
128 cloning subrepo s from $TESTTMP/gitroot
129 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
129 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
130
130
131 $ cd ../tb/s
131 $ cd ../tb/s
132 $ hg status --subrepos
132 $ hg status --subrepos
133 $ echo f > f
133 $ echo f > f
134 $ hg status --subrepos
134 $ hg status --subrepos
135 ? s/f
135 ? s/f
136 $ hg add .
136 $ hg add .
137 adding f
137 $ git add f
138 $ git add f
138 $ cd ..
139 $ cd ..
139
140
140 $ hg status --subrepos
141 $ hg status --subrepos
141 A s/f
142 A s/f
142 $ hg commit --subrepos -m f
143 $ hg commit --subrepos -m f
143 committing subrepository s
144 committing subrepository s
144 $ hg debugsub
145 $ hg debugsub
145 path s
146 path s
146 source ../gitroot
147 source ../gitroot
147 revision aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
148 revision aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
148
149
149 user b push changes
150 user b push changes
150
151
151 $ hg push 2>/dev/null
152 $ hg push 2>/dev/null
152 pushing to $TESTTMP/t (glob)
153 pushing to $TESTTMP/t (glob)
153 pushing branch testing of subrepo s
154 pushing branch testing of subrepo s
154 searching for changes
155 searching for changes
155 adding changesets
156 adding changesets
156 adding manifests
157 adding manifests
157 adding file changes
158 adding file changes
158 added 1 changesets with 1 changes to 1 files
159 added 1 changesets with 1 changes to 1 files
159
160
160 user a pulls, merges, commits
161 user a pulls, merges, commits
161
162
162 $ cd ../ta
163 $ cd ../ta
163 $ hg pull
164 $ hg pull
164 pulling from $TESTTMP/t (glob)
165 pulling from $TESTTMP/t (glob)
165 searching for changes
166 searching for changes
166 adding changesets
167 adding changesets
167 adding manifests
168 adding manifests
168 adding file changes
169 adding file changes
169 added 1 changesets with 1 changes to 1 files (+1 heads)
170 added 1 changesets with 1 changes to 1 files (+1 heads)
170 (run 'hg heads' to see heads, 'hg merge' to merge)
171 (run 'hg heads' to see heads, 'hg merge' to merge)
171 $ hg merge 2>/dev/null
172 $ hg merge 2>/dev/null
172 subrepository s diverged (local revision: 7969594, remote revision: aa84837)
173 subrepository s diverged (local revision: 7969594, remote revision: aa84837)
173 (M)erge, keep (l)ocal or keep (r)emote? m
174 (M)erge, keep (l)ocal or keep (r)emote? m
174 pulling subrepo s from $TESTTMP/gitroot
175 pulling subrepo s from $TESTTMP/gitroot
175 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
176 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
176 (branch merge, don't forget to commit)
177 (branch merge, don't forget to commit)
177 $ cat s/f
178 $ cat s/f
178 f
179 f
179 $ cat s/g
180 $ cat s/g
180 g
181 g
181 gg
182 gg
182 ggg
183 ggg
183 $ hg commit --subrepos -m 'merge'
184 $ hg commit --subrepos -m 'merge'
184 committing subrepository s
185 committing subrepository s
185 $ hg status --subrepos --rev 1:5
186 $ hg status --subrepos --rev 1:5
186 M .hgsubstate
187 M .hgsubstate
187 M s/g
188 M s/g
188 A s/f
189 A s/f
189 $ hg debugsub
190 $ hg debugsub
190 path s
191 path s
191 source ../gitroot
192 source ../gitroot
192 revision f47b465e1bce645dbf37232a00574aa1546ca8d3
193 revision f47b465e1bce645dbf37232a00574aa1546ca8d3
193 $ hg push 2>/dev/null
194 $ hg push 2>/dev/null
194 pushing to $TESTTMP/t (glob)
195 pushing to $TESTTMP/t (glob)
195 pushing branch testing of subrepo s
196 pushing branch testing of subrepo s
196 searching for changes
197 searching for changes
197 adding changesets
198 adding changesets
198 adding manifests
199 adding manifests
199 adding file changes
200 adding file changes
200 added 2 changesets with 2 changes to 1 files
201 added 2 changesets with 2 changes to 1 files
201
202
202 make upstream git changes
203 make upstream git changes
203
204
204 $ cd ..
205 $ cd ..
205 $ git clone -q gitroot gitclone
206 $ git clone -q gitroot gitclone
206 $ cd gitclone
207 $ cd gitclone
207 $ echo ff >> f
208 $ echo ff >> f
208 $ git commit -q -a -m ff
209 $ git commit -q -a -m ff
209 $ echo fff >> f
210 $ echo fff >> f
210 $ git commit -q -a -m fff
211 $ git commit -q -a -m fff
211 $ git push origin testing 2>/dev/null
212 $ git push origin testing 2>/dev/null
212
213
213 make and push changes to hg without updating the subrepo
214 make and push changes to hg without updating the subrepo
214
215
215 $ cd ../t
216 $ cd ../t
216 $ hg clone . ../td 2>&1 | egrep -v '^Cloning into|^done\.'
217 $ hg clone . ../td 2>&1 | egrep -v '^Cloning into|^done\.'
217 updating to branch default
218 updating to branch default
218 cloning subrepo s from $TESTTMP/gitroot
219 cloning subrepo s from $TESTTMP/gitroot
219 checking out detached HEAD in subrepo s
220 checking out detached HEAD in subrepo s
220 check out a git branch if you intend to make changes
221 check out a git branch if you intend to make changes
221 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 $ cd ../td
223 $ cd ../td
223 $ echo aa >> a
224 $ echo aa >> a
224 $ hg commit -m aa
225 $ hg commit -m aa
225 $ hg push
226 $ hg push
226 pushing to $TESTTMP/t (glob)
227 pushing to $TESTTMP/t (glob)
227 searching for changes
228 searching for changes
228 adding changesets
229 adding changesets
229 adding manifests
230 adding manifests
230 adding file changes
231 adding file changes
231 added 1 changesets with 1 changes to 1 files
232 added 1 changesets with 1 changes to 1 files
232
233
233 sync to upstream git, distribute changes
234 sync to upstream git, distribute changes
234
235
235 $ cd ../ta
236 $ cd ../ta
236 $ hg pull -u -q
237 $ hg pull -u -q
237 $ cd s
238 $ cd s
238 $ git pull -q >/dev/null 2>/dev/null
239 $ git pull -q >/dev/null 2>/dev/null
239 $ cd ..
240 $ cd ..
240 $ hg commit -m 'git upstream sync'
241 $ hg commit -m 'git upstream sync'
241 $ hg debugsub
242 $ hg debugsub
242 path s
243 path s
243 source ../gitroot
244 source ../gitroot
244 revision 32a343883b74769118bb1d3b4b1fbf9156f4dddc
245 revision 32a343883b74769118bb1d3b4b1fbf9156f4dddc
245 $ hg push -q
246 $ hg push -q
246
247
247 $ cd ../tb
248 $ cd ../tb
248 $ hg pull -q
249 $ hg pull -q
249 $ hg update 2>/dev/null
250 $ hg update 2>/dev/null
250 pulling subrepo s from $TESTTMP/gitroot
251 pulling subrepo s from $TESTTMP/gitroot
251 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
252 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
252 $ hg debugsub
253 $ hg debugsub
253 path s
254 path s
254 source ../gitroot
255 source ../gitroot
255 revision 32a343883b74769118bb1d3b4b1fbf9156f4dddc
256 revision 32a343883b74769118bb1d3b4b1fbf9156f4dddc
256
257
257 create a new git branch
258 create a new git branch
258
259
259 $ cd s
260 $ cd s
260 $ git checkout -b b2
261 $ git checkout -b b2
261 Switched to a new branch 'b2'
262 Switched to a new branch 'b2'
262 $ echo a>a
263 $ echo a>a
263 $ git add a
264 $ git add a
264 $ git commit -qm 'add a'
265 $ git commit -qm 'add a'
265 $ cd ..
266 $ cd ..
266 $ hg commit -m 'add branch in s'
267 $ hg commit -m 'add branch in s'
267
268
268 pulling new git branch should not create tracking branch named 'origin/b2'
269 pulling new git branch should not create tracking branch named 'origin/b2'
269 (issue3870)
270 (issue3870)
270 $ cd ../td/s
271 $ cd ../td/s
271 $ git remote set-url origin $TESTTMP/tb/s
272 $ git remote set-url origin $TESTTMP/tb/s
272 $ git branch --no-track oldtesting
273 $ git branch --no-track oldtesting
273 $ cd ..
274 $ cd ..
274 $ hg pull -q ../tb
275 $ hg pull -q ../tb
275 $ hg up
276 $ hg up
276 From $TESTTMP/tb/s
277 From $TESTTMP/tb/s
277 * [new branch] b2 -> origin/b2
278 * [new branch] b2 -> origin/b2
278 Previous HEAD position was f47b465... merge
279 Previous HEAD position was f47b465... merge
279 Switched to a new branch 'b2'
280 Switched to a new branch 'b2'
280 pulling subrepo s from $TESTTMP/tb/s
281 pulling subrepo s from $TESTTMP/tb/s
281 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
282 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
282
283
283 update to a revision without the subrepo, keeping the local git repository
284 update to a revision without the subrepo, keeping the local git repository
284
285
285 $ cd ../t
286 $ cd ../t
286 $ hg up 0
287 $ hg up 0
287 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
288 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
288 $ ls -a s
289 $ ls -a s
289 .
290 .
290 ..
291 ..
291 .git
292 .git
292
293
293 $ hg up 2
294 $ hg up 2
294 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
295 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
295 $ ls -a s
296 $ ls -a s
296 .
297 .
297 ..
298 ..
298 .git
299 .git
299 g
300 g
300
301
301 archive subrepos
302 archive subrepos
302
303
303 $ cd ../tc
304 $ cd ../tc
304 $ hg pull -q
305 $ hg pull -q
305 $ hg archive --subrepos -r 5 ../archive 2>/dev/null
306 $ hg archive --subrepos -r 5 ../archive 2>/dev/null
306 pulling subrepo s from $TESTTMP/gitroot
307 pulling subrepo s from $TESTTMP/gitroot
307 $ cd ../archive
308 $ cd ../archive
308 $ cat s/f
309 $ cat s/f
309 f
310 f
310 $ cat s/g
311 $ cat s/g
311 g
312 g
312 gg
313 gg
313 ggg
314 ggg
314
315
315 $ hg -R ../tc archive --subrepo -r 5 -X ../tc/**f ../archive_x 2>/dev/null
316 $ hg -R ../tc archive --subrepo -r 5 -X ../tc/**f ../archive_x 2>/dev/null
316 $ find ../archive_x | sort | grep -v pax_global_header
317 $ find ../archive_x | sort | grep -v pax_global_header
317 ../archive_x
318 ../archive_x
318 ../archive_x/.hg_archival.txt
319 ../archive_x/.hg_archival.txt
319 ../archive_x/.hgsub
320 ../archive_x/.hgsub
320 ../archive_x/.hgsubstate
321 ../archive_x/.hgsubstate
321 ../archive_x/a
322 ../archive_x/a
322 ../archive_x/s
323 ../archive_x/s
323 ../archive_x/s/g
324 ../archive_x/s/g
324
325
325 create nested repo
326 create nested repo
326
327
327 $ cd ..
328 $ cd ..
328 $ hg init outer
329 $ hg init outer
329 $ cd outer
330 $ cd outer
330 $ echo b>b
331 $ echo b>b
331 $ hg add b
332 $ hg add b
332 $ hg commit -m b
333 $ hg commit -m b
333
334
334 $ hg clone ../t inner 2> /dev/null
335 $ hg clone ../t inner 2> /dev/null
335 updating to branch default
336 updating to branch default
336 cloning subrepo s from $TESTTMP/gitroot
337 cloning subrepo s from $TESTTMP/gitroot
337 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
338 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
338 $ echo inner = inner > .hgsub
339 $ echo inner = inner > .hgsub
339 $ hg add .hgsub
340 $ hg add .hgsub
340 $ hg commit -m 'nested sub'
341 $ hg commit -m 'nested sub'
341
342
342 nested commit
343 nested commit
343
344
344 $ echo ffff >> inner/s/f
345 $ echo ffff >> inner/s/f
345 $ hg status --subrepos
346 $ hg status --subrepos
346 M inner/s/f
347 M inner/s/f
347 $ hg commit --subrepos -m nested
348 $ hg commit --subrepos -m nested
348 committing subrepository inner
349 committing subrepository inner
349 committing subrepository inner/s (glob)
350 committing subrepository inner/s (glob)
350
351
351 nested archive
352 nested archive
352
353
353 $ hg archive --subrepos ../narchive
354 $ hg archive --subrepos ../narchive
354 $ ls ../narchive/inner/s | grep -v pax_global_header
355 $ ls ../narchive/inner/s | grep -v pax_global_header
355 f
356 f
356 g
357 g
357
358
358 relative source expansion
359 relative source expansion
359
360
360 $ cd ..
361 $ cd ..
361 $ mkdir d
362 $ mkdir d
362 $ hg clone t d/t 2> /dev/null
363 $ hg clone t d/t 2> /dev/null
363 updating to branch default
364 updating to branch default
364 cloning subrepo s from $TESTTMP/gitroot
365 cloning subrepo s from $TESTTMP/gitroot
365 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
366 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
366
367
367 Don't crash if the subrepo is missing
368 Don't crash if the subrepo is missing
368
369
369 $ hg clone t missing -q
370 $ hg clone t missing -q
370 $ cd missing
371 $ cd missing
371 $ rm -rf s
372 $ rm -rf s
372 $ hg status -S
373 $ hg status -S
373 $ hg sum | grep commit
374 $ hg sum | grep commit
374 commit: 1 subrepos
375 commit: 1 subrepos
375 $ hg push -q
376 $ hg push -q
376 abort: subrepo s is missing (in subrepo s)
377 abort: subrepo s is missing (in subrepo s)
377 [255]
378 [255]
378 $ hg commit --subrepos -qm missing
379 $ hg commit --subrepos -qm missing
379 abort: subrepo s is missing (in subrepo s)
380 abort: subrepo s is missing (in subrepo s)
380 [255]
381 [255]
381 $ hg update -C 2> /dev/null
382 $ hg update -C 2> /dev/null
382 cloning subrepo s from $TESTTMP/gitroot
383 cloning subrepo s from $TESTTMP/gitroot
383 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
384 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
384 $ hg sum | grep commit
385 $ hg sum | grep commit
385 commit: (clean)
386 commit: (clean)
386
387
387 Don't crash if the .hgsubstate entry is missing
388 Don't crash if the .hgsubstate entry is missing
388
389
389 $ hg update 1 -q
390 $ hg update 1 -q
390 $ hg rm .hgsubstate
391 $ hg rm .hgsubstate
391 $ hg commit .hgsubstate -m 'no substate'
392 $ hg commit .hgsubstate -m 'no substate'
392 nothing changed
393 nothing changed
393 [1]
394 [1]
394 $ hg tag -l nosubstate
395 $ hg tag -l nosubstate
395 $ hg manifest
396 $ hg manifest
396 .hgsub
397 .hgsub
397 .hgsubstate
398 .hgsubstate
398 a
399 a
399
400
400 $ hg status -S
401 $ hg status -S
401 R .hgsubstate
402 R .hgsubstate
402 $ hg sum | grep commit
403 $ hg sum | grep commit
403 commit: 1 removed, 1 subrepos (new branch head)
404 commit: 1 removed, 1 subrepos (new branch head)
404
405
405 $ hg commit -m 'restore substate'
406 $ hg commit -m 'restore substate'
406 nothing changed
407 nothing changed
407 [1]
408 [1]
408 $ hg manifest
409 $ hg manifest
409 .hgsub
410 .hgsub
410 .hgsubstate
411 .hgsubstate
411 a
412 a
412 $ hg sum | grep commit
413 $ hg sum | grep commit
413 commit: 1 removed, 1 subrepos (new branch head)
414 commit: 1 removed, 1 subrepos (new branch head)
414
415
415 $ hg update -qC nosubstate
416 $ hg update -qC nosubstate
416 $ ls s
417 $ ls s
417 g
418 g
418
419
419 issue3109: false positives in git diff-index
420 issue3109: false positives in git diff-index
420
421
421 $ hg update -q
422 $ hg update -q
422 $ touch -t 200001010000 s/g
423 $ touch -t 200001010000 s/g
423 $ hg status --subrepos
424 $ hg status --subrepos
424 $ touch -t 200001010000 s/g
425 $ touch -t 200001010000 s/g
425 $ hg sum | grep commit
426 $ hg sum | grep commit
426 commit: (clean)
427 commit: (clean)
427
428
428 Check hg update --clean
429 Check hg update --clean
429 $ cd $TESTTMP/ta
430 $ cd $TESTTMP/ta
430 $ echo > s/g
431 $ echo > s/g
431 $ cd s
432 $ cd s
432 $ echo c1 > f1
433 $ echo c1 > f1
433 $ echo c1 > f2
434 $ echo c1 > f2
434 $ git add f1
435 $ git add f1
435 $ cd ..
436 $ cd ..
436 $ hg status -S
437 $ hg status -S
437 M s/g
438 M s/g
438 A s/f1
439 A s/f1
439 ? s/f2
440 ? s/f2
440 $ ls s
441 $ ls s
441 f
442 f
442 f1
443 f1
443 f2
444 f2
444 g
445 g
445 $ hg update --clean
446 $ hg update --clean
446 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
447 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
447 $ hg status -S
448 $ hg status -S
448 ? s/f1
449 ? s/f1
449 ? s/f2
450 ? s/f2
450 $ ls s
451 $ ls s
451 f
452 f
452 f1
453 f1
453 f2
454 f2
454 g
455 g
455
456
456 Sticky subrepositories, no changes
457 Sticky subrepositories, no changes
457 $ cd $TESTTMP/ta
458 $ cd $TESTTMP/ta
458 $ hg id -n
459 $ hg id -n
459 7
460 7
460 $ cd s
461 $ cd s
461 $ git rev-parse HEAD
462 $ git rev-parse HEAD
462 32a343883b74769118bb1d3b4b1fbf9156f4dddc
463 32a343883b74769118bb1d3b4b1fbf9156f4dddc
463 $ cd ..
464 $ cd ..
464 $ hg update 1 > /dev/null 2>&1
465 $ hg update 1 > /dev/null 2>&1
465 $ hg id -n
466 $ hg id -n
466 1
467 1
467 $ cd s
468 $ cd s
468 $ git rev-parse HEAD
469 $ git rev-parse HEAD
469 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
470 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
470 $ cd ..
471 $ cd ..
471
472
472 Sticky subrepositories, file changes
473 Sticky subrepositories, file changes
473 $ touch s/f1
474 $ touch s/f1
474 $ cd s
475 $ cd s
475 $ git add f1
476 $ git add f1
476 $ cd ..
477 $ cd ..
477 $ hg id -n
478 $ hg id -n
478 1+
479 1+
479 $ cd s
480 $ cd s
480 $ git rev-parse HEAD
481 $ git rev-parse HEAD
481 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
482 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
482 $ cd ..
483 $ cd ..
483 $ hg update 4
484 $ hg update 4
484 subrepository s diverged (local revision: da5f5b1, remote revision: aa84837)
485 subrepository s diverged (local revision: da5f5b1, remote revision: aa84837)
485 (M)erge, keep (l)ocal or keep (r)emote? m
486 (M)erge, keep (l)ocal or keep (r)emote? m
486 subrepository sources for s differ
487 subrepository sources for s differ
487 use (l)ocal source (da5f5b1) or (r)emote source (aa84837)? l
488 use (l)ocal source (da5f5b1) or (r)emote source (aa84837)? l
488 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
489 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
489 $ hg id -n
490 $ hg id -n
490 4+
491 4+
491 $ cd s
492 $ cd s
492 $ git rev-parse HEAD
493 $ git rev-parse HEAD
493 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
494 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
494 $ cd ..
495 $ cd ..
495 $ hg update --clean tip > /dev/null 2>&1
496 $ hg update --clean tip > /dev/null 2>&1
496
497
497 Sticky subrepository, revision updates
498 Sticky subrepository, revision updates
498 $ hg id -n
499 $ hg id -n
499 7
500 7
500 $ cd s
501 $ cd s
501 $ git rev-parse HEAD
502 $ git rev-parse HEAD
502 32a343883b74769118bb1d3b4b1fbf9156f4dddc
503 32a343883b74769118bb1d3b4b1fbf9156f4dddc
503 $ cd ..
504 $ cd ..
504 $ cd s
505 $ cd s
505 $ git checkout aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
506 $ git checkout aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
506 Previous HEAD position was 32a3438... fff
507 Previous HEAD position was 32a3438... fff
507 HEAD is now at aa84837... f
508 HEAD is now at aa84837... f
508 $ cd ..
509 $ cd ..
509 $ hg update 1
510 $ hg update 1
510 subrepository s diverged (local revision: 32a3438, remote revision: da5f5b1)
511 subrepository s diverged (local revision: 32a3438, remote revision: da5f5b1)
511 (M)erge, keep (l)ocal or keep (r)emote? m
512 (M)erge, keep (l)ocal or keep (r)emote? m
512 subrepository sources for s differ (in checked out version)
513 subrepository sources for s differ (in checked out version)
513 use (l)ocal source (32a3438) or (r)emote source (da5f5b1)? l
514 use (l)ocal source (32a3438) or (r)emote source (da5f5b1)? l
514 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
515 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
515 $ hg id -n
516 $ hg id -n
516 1+
517 1+
517 $ cd s
518 $ cd s
518 $ git rev-parse HEAD
519 $ git rev-parse HEAD
519 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
520 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
520 $ cd ..
521 $ cd ..
521
522
522 Sticky subrepository, file changes and revision updates
523 Sticky subrepository, file changes and revision updates
523 $ touch s/f1
524 $ touch s/f1
524 $ cd s
525 $ cd s
525 $ git add f1
526 $ git add f1
526 $ git rev-parse HEAD
527 $ git rev-parse HEAD
527 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
528 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
528 $ cd ..
529 $ cd ..
529 $ hg id -n
530 $ hg id -n
530 1+
531 1+
531 $ hg update 7
532 $ hg update 7
532 subrepository s diverged (local revision: 32a3438, remote revision: 32a3438)
533 subrepository s diverged (local revision: 32a3438, remote revision: 32a3438)
533 (M)erge, keep (l)ocal or keep (r)emote? m
534 (M)erge, keep (l)ocal or keep (r)emote? m
534 subrepository sources for s differ
535 subrepository sources for s differ
535 use (l)ocal source (32a3438) or (r)emote source (32a3438)? l
536 use (l)ocal source (32a3438) or (r)emote source (32a3438)? l
536 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
537 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
537 $ hg id -n
538 $ hg id -n
538 7+
539 7+
539 $ cd s
540 $ cd s
540 $ git rev-parse HEAD
541 $ git rev-parse HEAD
541 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
542 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
542 $ cd ..
543 $ cd ..
543
544
544 Sticky repository, update --clean
545 Sticky repository, update --clean
545 $ hg update --clean tip 2>/dev/null
546 $ hg update --clean tip 2>/dev/null
546 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
547 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
547 $ hg id -n
548 $ hg id -n
548 7
549 7
549 $ cd s
550 $ cd s
550 $ git rev-parse HEAD
551 $ git rev-parse HEAD
551 32a343883b74769118bb1d3b4b1fbf9156f4dddc
552 32a343883b74769118bb1d3b4b1fbf9156f4dddc
552 $ cd ..
553 $ cd ..
553
554
554 Test subrepo already at intended revision:
555 Test subrepo already at intended revision:
555 $ cd s
556 $ cd s
556 $ git checkout 32a343883b74769118bb1d3b4b1fbf9156f4dddc
557 $ git checkout 32a343883b74769118bb1d3b4b1fbf9156f4dddc
557 HEAD is now at 32a3438... fff
558 HEAD is now at 32a3438... fff
558 $ cd ..
559 $ cd ..
559 $ hg update 1
560 $ hg update 1
560 Previous HEAD position was 32a3438... fff
561 Previous HEAD position was 32a3438... fff
561 HEAD is now at da5f5b1... g
562 HEAD is now at da5f5b1... g
562 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
563 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
563 $ hg id -n
564 $ hg id -n
564 1
565 1
565 $ cd s
566 $ cd s
566 $ git rev-parse HEAD
567 $ git rev-parse HEAD
567 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
568 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
568 $ cd ..
569 $ cd ..
569
570
570 Test forgetting files, not implemented in git subrepo, used to
571 Test forgetting files, not implemented in git subrepo, used to
571 traceback
572 traceback
572 #if no-windows
573 #if no-windows
573 $ hg forget 'notafile*'
574 $ hg forget 'notafile*'
574 notafile*: No such file or directory
575 notafile*: No such file or directory
575 [1]
576 [1]
576 #else
577 #else
577 $ hg forget 'notafile'
578 $ hg forget 'notafile'
578 notafile: * (glob)
579 notafile: * (glob)
579 [1]
580 [1]
580 #endif
581 #endif
581
582
582 $ cd ..
583 $ cd ..
583
584
584 Test sanitizing ".hg/hgrc" in subrepo
585 Test sanitizing ".hg/hgrc" in subrepo
585
586
586 $ cd t
587 $ cd t
587 $ hg tip -q
588 $ hg tip -q
588 7:af6d2edbb0d3
589 7:af6d2edbb0d3
589 $ hg update -q -C af6d2edbb0d3
590 $ hg update -q -C af6d2edbb0d3
590 $ cd s
591 $ cd s
591 $ git checkout -q -b sanitize-test
592 $ git checkout -q -b sanitize-test
592 $ mkdir .hg
593 $ mkdir .hg
593 $ echo '.hg/hgrc in git repo' > .hg/hgrc
594 $ echo '.hg/hgrc in git repo' > .hg/hgrc
594 $ mkdir -p sub/.hg
595 $ mkdir -p sub/.hg
595 $ echo 'sub/.hg/hgrc in git repo' > sub/.hg/hgrc
596 $ echo 'sub/.hg/hgrc in git repo' > sub/.hg/hgrc
596 $ git add .hg sub
597 $ git add .hg sub
597 $ git commit -qm 'add .hg/hgrc to be sanitized at hg update'
598 $ git commit -qm 'add .hg/hgrc to be sanitized at hg update'
598 $ git push -q origin sanitize-test
599 $ git push -q origin sanitize-test
599 $ cd ..
600 $ cd ..
600 $ grep ' s$' .hgsubstate
601 $ grep ' s$' .hgsubstate
601 32a343883b74769118bb1d3b4b1fbf9156f4dddc s
602 32a343883b74769118bb1d3b4b1fbf9156f4dddc s
602 $ hg commit -qm 'commit with git revision including .hg/hgrc'
603 $ hg commit -qm 'commit with git revision including .hg/hgrc'
603 $ hg parents -q
604 $ hg parents -q
604 8:3473d20bddcf
605 8:3473d20bddcf
605 $ grep ' s$' .hgsubstate
606 $ grep ' s$' .hgsubstate
606 c4069473b459cf27fd4d7c2f50c4346b4e936599 s
607 c4069473b459cf27fd4d7c2f50c4346b4e936599 s
607 $ cd ..
608 $ cd ..
608
609
609 $ hg -R tc pull -q
610 $ hg -R tc pull -q
610 $ hg -R tc update -q -C 3473d20bddcf 2>&1 | sort
611 $ hg -R tc update -q -C 3473d20bddcf 2>&1 | sort
611 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/.hg' (glob)
612 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/.hg' (glob)
612 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/sub/.hg' (glob)
613 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/sub/.hg' (glob)
613 $ cd tc
614 $ cd tc
614 $ hg parents -q
615 $ hg parents -q
615 8:3473d20bddcf
616 8:3473d20bddcf
616 $ grep ' s$' .hgsubstate
617 $ grep ' s$' .hgsubstate
617 c4069473b459cf27fd4d7c2f50c4346b4e936599 s
618 c4069473b459cf27fd4d7c2f50c4346b4e936599 s
618 $ test -f s/.hg/hgrc
619 $ test -f s/.hg/hgrc
619 [1]
620 [1]
620 $ test -f s/sub/.hg/hgrc
621 $ test -f s/sub/.hg/hgrc
621 [1]
622 [1]
622 $ cd ..
623 $ cd ..
623
624
624 additional test for "git merge --ff" route:
625 additional test for "git merge --ff" route:
625
626
626 $ cd t
627 $ cd t
627 $ hg tip -q
628 $ hg tip -q
628 8:3473d20bddcf
629 8:3473d20bddcf
629 $ hg update -q -C af6d2edbb0d3
630 $ hg update -q -C af6d2edbb0d3
630 $ cd s
631 $ cd s
631 $ git checkout -q testing
632 $ git checkout -q testing
632 $ mkdir .hg
633 $ mkdir .hg
633 $ echo '.hg/hgrc in git repo' > .hg/hgrc
634 $ echo '.hg/hgrc in git repo' > .hg/hgrc
634 $ mkdir -p sub/.hg
635 $ mkdir -p sub/.hg
635 $ echo 'sub/.hg/hgrc in git repo' > sub/.hg/hgrc
636 $ echo 'sub/.hg/hgrc in git repo' > sub/.hg/hgrc
636 $ git add .hg sub
637 $ git add .hg sub
637 $ git commit -qm 'add .hg/hgrc to be sanitized at hg update (git merge --ff)'
638 $ git commit -qm 'add .hg/hgrc to be sanitized at hg update (git merge --ff)'
638 $ git push -q origin testing
639 $ git push -q origin testing
639 $ cd ..
640 $ cd ..
640 $ grep ' s$' .hgsubstate
641 $ grep ' s$' .hgsubstate
641 32a343883b74769118bb1d3b4b1fbf9156f4dddc s
642 32a343883b74769118bb1d3b4b1fbf9156f4dddc s
642 $ hg commit -qm 'commit with git revision including .hg/hgrc'
643 $ hg commit -qm 'commit with git revision including .hg/hgrc'
643 $ hg parents -q
644 $ hg parents -q
644 9:ed23f7fe024e
645 9:ed23f7fe024e
645 $ grep ' s$' .hgsubstate
646 $ grep ' s$' .hgsubstate
646 f262643c1077219fbd3858d54e78ef050ef84fbf s
647 f262643c1077219fbd3858d54e78ef050ef84fbf s
647 $ cd ..
648 $ cd ..
648
649
649 $ cd tc
650 $ cd tc
650 $ hg update -q -C af6d2edbb0d3
651 $ hg update -q -C af6d2edbb0d3
651 $ test -f s/.hg/hgrc
652 $ test -f s/.hg/hgrc
652 [1]
653 [1]
653 $ test -f s/sub/.hg/hgrc
654 $ test -f s/sub/.hg/hgrc
654 [1]
655 [1]
655 $ cd ..
656 $ cd ..
656 $ hg -R tc pull -q
657 $ hg -R tc pull -q
657 $ hg -R tc update -q -C ed23f7fe024e 2>&1 | sort
658 $ hg -R tc update -q -C ed23f7fe024e 2>&1 | sort
658 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/.hg' (glob)
659 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/.hg' (glob)
659 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/sub/.hg' (glob)
660 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/sub/.hg' (glob)
660 $ cd tc
661 $ cd tc
661 $ hg parents -q
662 $ hg parents -q
662 9:ed23f7fe024e
663 9:ed23f7fe024e
663 $ grep ' s$' .hgsubstate
664 $ grep ' s$' .hgsubstate
664 f262643c1077219fbd3858d54e78ef050ef84fbf s
665 f262643c1077219fbd3858d54e78ef050ef84fbf s
665 $ test -f s/.hg/hgrc
666 $ test -f s/.hg/hgrc
666 [1]
667 [1]
667 $ test -f s/sub/.hg/hgrc
668 $ test -f s/sub/.hg/hgrc
668 [1]
669 [1]
669
670
670 Test that sanitizing is omitted in meta data area:
671 Test that sanitizing is omitted in meta data area:
671
672
672 $ mkdir s/.git/.hg
673 $ mkdir s/.git/.hg
673 $ echo '.hg/hgrc in git metadata area' > s/.git/.hg/hgrc
674 $ echo '.hg/hgrc in git metadata area' > s/.git/.hg/hgrc
674 $ hg update -q -C af6d2edbb0d3
675 $ hg update -q -C af6d2edbb0d3
675 checking out detached HEAD in subrepo s
676 checking out detached HEAD in subrepo s
676 check out a git branch if you intend to make changes
677 check out a git branch if you intend to make changes
677
678
678 check differences made by most recent change
679 check differences made by most recent change
679 $ cd s
680 $ cd s
680 $ cat > foobar << EOF
681 $ cat > foobar << EOF
681 > woopwoop
682 > woopwoop
682 >
683 >
683 > foo
684 > foo
684 > bar
685 > bar
685 > EOF
686 > EOF
686 $ git add foobar
687 $ git add foobar
687 $ cd ..
688 $ cd ..
688
689
689 $ hg diff --subrepos
690 $ hg diff --subrepos
690 diff --git a/s/foobar b/s/foobar
691 diff --git a/s/foobar b/s/foobar
691 new file mode 100644
692 new file mode 100644
692 index 0000000..8a5a5e2
693 index 0000000..8a5a5e2
693 --- /dev/null
694 --- /dev/null
694 +++ b/s/foobar
695 +++ b/s/foobar
695 @@ -0,0 +1,4 @@
696 @@ -0,0 +1,4 @@
696 +woopwoop
697 +woopwoop
697 +
698 +
698 +foo
699 +foo
699 +bar
700 +bar
700
701
701 $ hg commit --subrepos -m "Added foobar"
702 $ hg commit --subrepos -m "Added foobar"
702 committing subrepository s
703 committing subrepository s
703 created new head
704 created new head
704
705
705 $ hg diff -c . --subrepos --nodates
706 $ hg diff -c . --subrepos --nodates
706 diff -r af6d2edbb0d3 -r 255ee8cf690e .hgsubstate
707 diff -r af6d2edbb0d3 -r 255ee8cf690e .hgsubstate
707 --- a/.hgsubstate
708 --- a/.hgsubstate
708 +++ b/.hgsubstate
709 +++ b/.hgsubstate
709 @@ -1,1 +1,1 @@
710 @@ -1,1 +1,1 @@
710 -32a343883b74769118bb1d3b4b1fbf9156f4dddc s
711 -32a343883b74769118bb1d3b4b1fbf9156f4dddc s
711 +fd4dbf828a5b2fcd36b2bcf21ea773820970d129 s
712 +fd4dbf828a5b2fcd36b2bcf21ea773820970d129 s
712 diff --git a/s/foobar b/s/foobar
713 diff --git a/s/foobar b/s/foobar
713 new file mode 100644
714 new file mode 100644
714 index 0000000..8a5a5e2
715 index 0000000..8a5a5e2
715 --- /dev/null
716 --- /dev/null
716 +++ b/s/foobar
717 +++ b/s/foobar
717 @@ -0,0 +1,4 @@
718 @@ -0,0 +1,4 @@
718 +woopwoop
719 +woopwoop
719 +
720 +
720 +foo
721 +foo
721 +bar
722 +bar
722
723
723 check output when only diffing the subrepository
724 check output when only diffing the subrepository
724 $ hg diff -c . --subrepos s
725 $ hg diff -c . --subrepos s
725 diff --git a/s/foobar b/s/foobar
726 diff --git a/s/foobar b/s/foobar
726 new file mode 100644
727 new file mode 100644
727 index 0000000..8a5a5e2
728 index 0000000..8a5a5e2
728 --- /dev/null
729 --- /dev/null
729 +++ b/s/foobar
730 +++ b/s/foobar
730 @@ -0,0 +1,4 @@
731 @@ -0,0 +1,4 @@
731 +woopwoop
732 +woopwoop
732 +
733 +
733 +foo
734 +foo
734 +bar
735 +bar
735
736
736 check output when diffing something else
737 check output when diffing something else
737 $ hg diff -c . --subrepos .hgsubstate --nodates
738 $ hg diff -c . --subrepos .hgsubstate --nodates
738 diff -r af6d2edbb0d3 -r 255ee8cf690e .hgsubstate
739 diff -r af6d2edbb0d3 -r 255ee8cf690e .hgsubstate
739 --- a/.hgsubstate
740 --- a/.hgsubstate
740 +++ b/.hgsubstate
741 +++ b/.hgsubstate
741 @@ -1,1 +1,1 @@
742 @@ -1,1 +1,1 @@
742 -32a343883b74769118bb1d3b4b1fbf9156f4dddc s
743 -32a343883b74769118bb1d3b4b1fbf9156f4dddc s
743 +fd4dbf828a5b2fcd36b2bcf21ea773820970d129 s
744 +fd4dbf828a5b2fcd36b2bcf21ea773820970d129 s
744
745
745 add new changes, including whitespace
746 add new changes, including whitespace
746 $ cd s
747 $ cd s
747 $ cat > foobar << EOF
748 $ cat > foobar << EOF
748 > woop woop
749 > woop woop
749 >
750 >
750 > foo
751 > foo
751 > bar
752 > bar
752 > EOF
753 > EOF
753 $ echo foo > barfoo
754 $ echo foo > barfoo
754 $ git add barfoo
755 $ git add barfoo
755 $ cd ..
756 $ cd ..
756
757
757 $ hg diff --subrepos --ignore-all-space
758 $ hg diff --subrepos --ignore-all-space
758 diff --git a/s/barfoo b/s/barfoo
759 diff --git a/s/barfoo b/s/barfoo
759 new file mode 100644
760 new file mode 100644
760 index 0000000..257cc56
761 index 0000000..257cc56
761 --- /dev/null
762 --- /dev/null
762 +++ b/s/barfoo
763 +++ b/s/barfoo
763 @@ -0,0 +1 @@
764 @@ -0,0 +1 @@
764 +foo
765 +foo
765 $ hg diff --subrepos s/foobar
766 $ hg diff --subrepos s/foobar
766 diff --git a/s/foobar b/s/foobar
767 diff --git a/s/foobar b/s/foobar
767 index 8a5a5e2..bd5812a 100644
768 index 8a5a5e2..bd5812a 100644
768 --- a/s/foobar
769 --- a/s/foobar
769 +++ b/s/foobar
770 +++ b/s/foobar
770 @@ -1,4 +1,4 @@
771 @@ -1,4 +1,4 @@
771 -woopwoop
772 -woopwoop
772 +woop woop
773 +woop woop
773
774
774 foo
775 foo
775 bar
776 bar
776
777
777 execute a diffstat
778 execute a diffstat
778 the output contains a regex, because git 1.7.10 and 1.7.11
779 the output contains a regex, because git 1.7.10 and 1.7.11
779 change the amount of whitespace
780 change the amount of whitespace
780 $ hg diff --subrepos --stat
781 $ hg diff --subrepos --stat
781 \s*barfoo |\s*1 + (re)
782 \s*barfoo |\s*1 + (re)
782 \s*foobar |\s*2 +- (re)
783 \s*foobar |\s*2 +- (re)
783 2 files changed, 2 insertions\(\+\), 1 deletions?\(-\) (re)
784 2 files changed, 2 insertions\(\+\), 1 deletions?\(-\) (re)
784
785
785 ensure adding include/exclude ignores the subrepo
786 ensure adding include/exclude ignores the subrepo
786 $ hg diff --subrepos -I s/foobar
787 $ hg diff --subrepos -I s/foobar
787 $ hg diff --subrepos -X s/foobar
788 $ hg diff --subrepos -X s/foobar
788
789
789 revert the subrepository
790 revert the subrepository
790 $ hg revert --all
791 $ hg revert --all
791 reverting subrepo ../gitroot
792 reverting subrepo ../gitroot
792
793
793 $ hg status --subrepos
794 $ hg status --subrepos
794 ? s/barfoo
795 ? s/barfoo
795 ? s/foobar.orig
796 ? s/foobar.orig
796
797
797 $ mv s/foobar.orig s/foobar
798 $ mv s/foobar.orig s/foobar
798
799
799 $ hg revert --no-backup s
800 $ hg revert --no-backup s
800 reverting subrepo ../gitroot
801 reverting subrepo ../gitroot
801
802
802 $ hg status --subrepos
803 $ hg status --subrepos
803 ? s/barfoo
804 ? s/barfoo
804
805
805 show file at specific revision
806 show file at specific revision
806 $ cat > s/foobar << EOF
807 $ cat > s/foobar << EOF
807 > woop woop
808 > woop woop
808 > fooo bar
809 > fooo bar
809 > EOF
810 > EOF
810 $ hg commit --subrepos -m "updated foobar"
811 $ hg commit --subrepos -m "updated foobar"
811 committing subrepository s
812 committing subrepository s
812 $ cat > s/foobar << EOF
813 $ cat > s/foobar << EOF
813 > current foobar
814 > current foobar
814 > (should not be visible using hg cat)
815 > (should not be visible using hg cat)
815 > EOF
816 > EOF
816
817
817 $ hg cat -r . s/foobar
818 $ hg cat -r . s/foobar
818 woop woop
819 woop woop
819 fooo bar (no-eol)
820 fooo bar (no-eol)
820 $ hg cat -r "parents(.)" s/foobar > catparents
821 $ hg cat -r "parents(.)" s/foobar > catparents
821
822
822 $ mkdir -p tmp/s
823 $ mkdir -p tmp/s
823
824
824 $ hg cat -r "parents(.)" --output tmp/%% s/foobar
825 $ hg cat -r "parents(.)" --output tmp/%% s/foobar
825 $ diff tmp/% catparents
826 $ diff tmp/% catparents
826
827
827 $ hg cat -r "parents(.)" --output tmp/%s s/foobar
828 $ hg cat -r "parents(.)" --output tmp/%s s/foobar
828 $ diff tmp/foobar catparents
829 $ diff tmp/foobar catparents
829
830
830 $ hg cat -r "parents(.)" --output tmp/%d/otherfoobar s/foobar
831 $ hg cat -r "parents(.)" --output tmp/%d/otherfoobar s/foobar
831 $ diff tmp/s/otherfoobar catparents
832 $ diff tmp/s/otherfoobar catparents
832
833
833 $ hg cat -r "parents(.)" --output tmp/%p s/foobar
834 $ hg cat -r "parents(.)" --output tmp/%p s/foobar
834 $ diff tmp/s/foobar catparents
835 $ diff tmp/s/foobar catparents
835
836
836 $ hg cat -r "parents(.)" --output tmp/%H s/foobar
837 $ hg cat -r "parents(.)" --output tmp/%H s/foobar
837 $ diff tmp/255ee8cf690ec86e99b1e80147ea93ece117cd9d catparents
838 $ diff tmp/255ee8cf690ec86e99b1e80147ea93ece117cd9d catparents
838
839
839 $ hg cat -r "parents(.)" --output tmp/%R s/foobar
840 $ hg cat -r "parents(.)" --output tmp/%R s/foobar
840 $ diff tmp/10 catparents
841 $ diff tmp/10 catparents
841
842
842 $ hg cat -r "parents(.)" --output tmp/%h s/foobar
843 $ hg cat -r "parents(.)" --output tmp/%h s/foobar
843 $ diff tmp/255ee8cf690e catparents
844 $ diff tmp/255ee8cf690e catparents
844
845
845 $ rm tmp/10
846 $ rm tmp/10
846 $ hg cat -r "parents(.)" --output tmp/%r s/foobar
847 $ hg cat -r "parents(.)" --output tmp/%r s/foobar
847 $ diff tmp/10 catparents
848 $ diff tmp/10 catparents
848
849
849 $ mkdir tmp/tc
850 $ mkdir tmp/tc
850 $ hg cat -r "parents(.)" --output tmp/%b/foobar s/foobar
851 $ hg cat -r "parents(.)" --output tmp/%b/foobar s/foobar
851 $ diff tmp/tc/foobar catparents
852 $ diff tmp/tc/foobar catparents
852
853
854 cleanup
855 $ rm -r tmp
856 $ rm catparents
857
858 add git files, using either files or patterns
859 $ echo "hsss! hsssssssh!" > s/snake.python
860 $ echo "ccc" > s/c.c
861 $ echo "cpp" > s/cpp.cpp
862
863 $ hg add s/snake.python s/c.c s/cpp.cpp
864 $ hg st --subrepos s
865 M s/foobar
866 A s/c.c
867 A s/cpp.cpp
868 A s/snake.python
869 ? s/barfoo
870 $ hg revert s
871 reverting subrepo ../gitroot
872
873 $ hg add --subrepos "glob:**.python"
874 adding s/snake.python (glob)
875 $ hg st --subrepos s
876 A s/snake.python
877 ? s/barfoo
878 ? s/c.c
879 ? s/cpp.cpp
880 ? s/foobar.orig
881 $ hg revert s
882 reverting subrepo ../gitroot
883
884 $ hg add --subrepos s
885 adding s/barfoo (glob)
886 adding s/c.c (glob)
887 adding s/cpp.cpp (glob)
888 adding s/foobar.orig (glob)
889 adding s/snake.python (glob)
890 $ hg st --subrepos s
891 A s/barfoo
892 A s/c.c
893 A s/cpp.cpp
894 A s/foobar.orig
895 A s/snake.python
896 $ hg revert s
897 reverting subrepo ../gitroot
898 make sure everything is reverted correctly
899 $ hg st --subrepos s
900 ? s/barfoo
901 ? s/c.c
902 ? s/cpp.cpp
903 ? s/foobar.orig
904 ? s/snake.python
905
906 $ hg add --subrepos --exclude "path:s/c.c"
907 adding s/barfoo (glob)
908 adding s/cpp.cpp (glob)
909 adding s/foobar.orig (glob)
910 adding s/snake.python (glob)
911 $ hg st --subrepos s
912 A s/barfoo
913 A s/cpp.cpp
914 A s/foobar.orig
915 A s/snake.python
916 ? s/c.c
917 $ hg revert --all -q
918
919 .hgignore should not have influence in subrepos
920 $ cat > .hgignore << EOF
921 > syntax: glob
922 > *.python
923 > EOF
924 $ hg add .hgignore
925 $ hg add --subrepos "glob:**.python"
926 adding s/snake.python (glob)
927 $ hg st --subrepos s
928 A s/snake.python
929 ? s/barfoo
930 ? s/c.c
931 ? s/cpp.cpp
932 ? s/foobar.orig
933 $ hg revert --all -q
934
935 .gitignore should have influence,
936 except for explicitly added files (no patterns)
937 $ cat > s/.gitignore << EOF
938 > *.python
939 > EOF
940 $ hg add s/.gitignore
941 $ hg st --subrepos s
942 A s/.gitignore
943 ? s/barfoo
944 ? s/c.c
945 ? s/cpp.cpp
946 ? s/foobar.orig
947 $ hg add --subrepos "glob:**.python"
948 $ hg st --subrepos s
949 A s/.gitignore
950 ? s/barfoo
951 ? s/c.c
952 ? s/cpp.cpp
953 ? s/foobar.orig
954 $ hg add --subrepos s/snake.python
955 $ hg st --subrepos s
956 A s/.gitignore
957 A s/snake.python
958 ? s/barfoo
959 ? s/c.c
960 ? s/cpp.cpp
961 ? s/foobar.orig
962
963 correctly do a dry run
964 $ hg add --subrepos s --dry-run
965 adding s/barfoo (glob)
966 adding s/c.c (glob)
967 adding s/cpp.cpp (glob)
968 adding s/foobar.orig (glob)
969 $ hg st --subrepos s
970 A s/.gitignore
971 A s/snake.python
972 ? s/barfoo
973 ? s/c.c
974 ? s/cpp.cpp
975 ? s/foobar.orig
976
977 currently no error given when adding an already tracked file
978 $ hg add s/.gitignore
979
853 $ cd ..
980 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now