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