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