comparison hglib/client.py @ 224:2ab42323f149

client: handle commit messages with \0 characters for all commands Each of the impacted commands will now use the 'json' template, which they all support as of Mercurial 3.7.3 (the first version tested in the regression tests). Note: I tried to add a test with null bytes, but both hglib and using hg directly through subprocess rejected adding a commit message with a null byte.
author Mathias De Mare <mathias.de_mare@nokia.com>
date Mon, 13 Mar 2023 15:32:20 +0100
parents 68588c652ac6
children 484b56ac4aec
comparison
equal deleted inserted replaced
223:3f9dd44be8c2 224:2ab42323f149
157 return channel, length 157 return channel, length
158 else: 158 else:
159 return channel, self.server.stdout.read(length) 159 return channel, self.server.stdout.read(length)
160 160
161 @staticmethod 161 @staticmethod
162 def _parserevs(splitted):
163 '''splitted is a list of fields according to our rev.style, where
164 each 6 fields compose one revision.
165 '''
166 revs = []
167 for rev in util.grouper(7, splitted):
168 # truncate the timezone and convert to a local datetime
169 posixtime = float(rev[6].split(b('.'), 1)[0])
170 dt = datetime.datetime.fromtimestamp(posixtime)
171 revs.append(revision(rev[0], rev[1], rev[2], rev[3],
172 rev[4], rev[5], dt))
173 return revs
174
175 @staticmethod
176 def _parsejsonrevs(jsonrevs): 162 def _parsejsonrevs(jsonrevs):
177 revs = [] 163 revs = []
178 for rev in jsonrevs: 164 for rev in jsonrevs:
179 # truncate the timezone and convert to a local datetime 165 # truncate the timezone and convert to a local datetime
180 posixtime = float(rev["date"][0]) 166 posixtime = float(rev["date"][0])
927 """ 913 """
928 if not isinstance(rev, list): 914 if not isinstance(rev, list):
929 rev = [rev] 915 rev = [rev]
930 916
931 args = cmdbuilder(b('heads'), r=startrev, t=topological, c=closed, 917 args = cmdbuilder(b('heads'), r=startrev, t=topological, c=closed,
932 template=templates.changeset, hidden=self.hidden, 918 template="json", hidden=self.hidden,
933 *rev) 919 *rev)
934 920
935 def eh(ret, out, err): 921 def eh(ret, out, err):
936 if ret != 1: 922 if ret != 1:
937 raise error.CommandError(args, ret, out, err) 923 raise error.CommandError(args, ret, out, err)
938 return b('') 924 return b('')
939 925
940 out = self.rawcommand(args, eh=eh).split(b('\0'))[:-1] 926 out = self.rawcommand(args, eh=eh)
941 return self._parserevs(out) 927 if not out:
928 return []
929 json_out = json.loads(out)
930
931 return self._parsejsonrevs(json_out)
942 932
943 def identify(self, rev=None, source=None, num=False, id=False, branch=False, 933 def identify(self, rev=None, source=None, num=False, id=False, branch=False,
944 tags=False, bookmarks=False): 934 tags=False, bookmarks=False):
945 """Return a summary string identifying the repository state at rev 935 """Return a summary string identifying the repository state at rev
946 using one or two parent hash identifiers, followed by a "+" if 936 using one or two parent hash identifiers, followed by a "+" if
1034 insecure- do not verify server certificate (ignoring web.cacerts config) 1024 insecure- do not verify server certificate (ignoring web.cacerts config)
1035 subrepos - recurse into subrepositories 1025 subrepos - recurse into subrepositories
1036 1026
1037 """ 1027 """
1038 args = cmdbuilder(b('incoming'), path, 1028 args = cmdbuilder(b('incoming'), path,
1039 template=templates.changeset, r=revrange, 1029 template="json", r=revrange,
1040 f=force, n=newest, bundle=bundle, 1030 f=force, n=newest, bundle=bundle,
1041 B=bookmarks, b=branch, l=limit, M=nomerges, 1031 B=bookmarks, b=branch, l=limit, M=nomerges,
1042 S=subrepos) 1032 S=subrepos)
1043 1033
1044 def eh(ret, out, err): 1034 def eh(ret, out, err):
1048 out = self.rawcommand(args, eh=eh) 1038 out = self.rawcommand(args, eh=eh)
1049 if not out: 1039 if not out:
1050 return [] 1040 return []
1051 1041
1052 out = util.eatlines(out, 2) 1042 out = util.eatlines(out, 2)
1043
1053 if bookmarks: 1044 if bookmarks:
1054 bms = [] 1045 bms = []
1055 for line in out.splitlines(): 1046 for line in out.splitlines():
1056 bms.append(tuple(line.split())) 1047 bms.append(tuple(line.split()))
1057 return bms 1048 return bms
1058 else: 1049 else:
1059 out = out.split(b('\0'))[:-1] 1050 json_out = json.loads(out)
1060 return self._parserevs(out) 1051 return self._parsejsonrevs(json_out)
1061 1052
1062 def log(self, revrange=None, files=[], follow=False, 1053 def log(self, revrange=None, files=[], follow=False,
1063 followfirst=False, date=None, copies=False, keyword=None, 1054 followfirst=False, date=None, copies=False, keyword=None,
1064 removed=False, onlymerges=False, user=None, branch=None, 1055 removed=False, onlymerges=False, user=None, branch=None,
1065 prune=None, hidden=None, limit=None, nomerges=False, 1056 prune=None, hidden=None, limit=None, nomerges=False,
1233 subrepositories 1224 subrepositories
1234 1225
1235 """ 1226 """
1236 args = cmdbuilder(b('outgoing'), 1227 args = cmdbuilder(b('outgoing'),
1237 path, 1228 path,
1238 template=templates.changeset, r=revrange, 1229 template="json", r=revrange,
1239 f=force, n=newest, B=bookmarks, 1230 f=force, n=newest, B=bookmarks,
1240 b=branch, S=subrepos) 1231 b=branch, S=subrepos)
1241 1232
1242 def eh(ret, out, err): 1233 def eh(ret, out, err):
1243 if ret != 1: 1234 if ret != 1:
1252 bms = [] 1243 bms = []
1253 for line in out.splitlines(): 1244 for line in out.splitlines():
1254 bms.append(tuple(line.split())) 1245 bms.append(tuple(line.split()))
1255 return bms 1246 return bms
1256 else: 1247 else:
1257 out = out.split(b('\0'))[:-1] 1248 json_out = json.loads(out)
1258 return self._parserevs(out) 1249 return self._parsejsonrevs(json_out)
1259 1250
1260 def parents(self, rev=None, file=None): 1251 def parents(self, rev=None, file=None):
1261 """Return the working directory's parent revisions. If rev is given, 1252 """Return the working directory's parent revisions. If rev is given,
1262 the parent of that revision will be printed. If file is given, 1253 the parent of that revision will be printed. If file is given,
1263 the revision in which the file was last changed (before the 1254 the revision in which the file was last changed (before the
1264 working directory revision or the revision specified by rev) 1255 working directory revision or the revision specified by rev)
1265 is returned. 1256 is returned.
1266 1257
1267 """ 1258 """
1268 args = cmdbuilder(b('parents'), file, template=templates.changeset, 1259 args = cmdbuilder(b('parents'), file, template="json",
1269 r=rev, hidden=self.hidden) 1260 r=rev, hidden=self.hidden)
1270 1261
1271 out = self.rawcommand(args) 1262 out = self.rawcommand(args)
1272 if not out: 1263 if not out:
1273 return 1264 return
1274 1265
1275 out = out.split(b('\0'))[:-1] 1266 json_out = json.loads(out)
1276 1267
1277 return self._parserevs(out) 1268 if not json_out:
1269 return
1270
1271 return self._parsejsonrevs(json_out)
1278 1272
1279 def paths(self, name=None): 1273 def paths(self, name=None):
1280 """ 1274 """
1281 Return the definition of given symbolic path name. If no name is given, 1275 Return the definition of given symbolic path name. If no name is given,
1282 return a dictionary of pathname : url of all available names. 1276 return a dictionary of pathname : url of all available names.
1680 """ 1674 """
1681 Return the tip revision (usually just called the tip) which is the 1675 Return the tip revision (usually just called the tip) which is the
1682 changeset most recently added to the repository (and therefore the most 1676 changeset most recently added to the repository (and therefore the most
1683 recently changed head). 1677 recently changed head).
1684 """ 1678 """
1685 args = cmdbuilder(b('tip'), template=templates.changeset, 1679 args = cmdbuilder(b('tip'), template="json",
1686 hidden=self.hidden) 1680 hidden=self.hidden)
1687 out = self.rawcommand(args) 1681 out = self.rawcommand(args)
1688 out = out.split(b('\0')) 1682 json_out = json.loads(out)
1689 1683
1690 return self._parserevs(out)[0] 1684 return self._parsejsonrevs(json_out)[0]
1691 1685
1692 def update(self, rev=None, clean=False, check=False, date=None): 1686 def update(self, rev=None, clean=False, check=False, date=None):
1693 """ 1687 """
1694 Update the repository's working directory to changeset specified by rev. 1688 Update the repository's working directory to changeset specified by rev.
1695 If rev isn't specified, update to the tip of the current named branch. 1689 If rev isn't specified, update to the tip of the current named branch.