7 Module implementing the version control systems interface to Mercurial. |
7 Module implementing the version control systems interface to Mercurial. |
8 """ |
8 """ |
9 |
9 |
10 import os |
10 import os |
11 import shutil |
11 import shutil |
|
12 import contextlib |
12 |
13 |
13 from PyQt5.QtCore import ( |
14 from PyQt5.QtCore import ( |
14 pyqtSignal, QFileInfo, QFileSystemWatcher, QCoreApplication |
15 pyqtSignal, QFileInfo, QFileSystemWatcher, QCoreApplication |
15 ) |
16 ) |
16 from PyQt5.QtWidgets import QApplication, QDialog, QInputDialog |
17 from PyQt5.QtWidgets import QApplication, QDialog, QInputDialog |
745 being edited and has unsaved modification, they can be saved or the |
746 being edited and has unsaved modification, they can be saved or the |
746 operation may be aborted. |
747 operation may be aborted. |
747 |
748 |
748 @param name file/directory name to be diffed (string) |
749 @param name file/directory name to be diffed (string) |
749 """ |
750 """ |
750 if isinstance(name, list): |
751 names = name[:] if isinstance(name, list) else [name] |
751 names = name[:] |
|
752 else: |
|
753 names = [name] |
|
754 for nam in names: |
752 for nam in names: |
755 if os.path.isfile(nam): |
753 if os.path.isfile(nam): |
756 editor = e5App().getObject("ViewManager").getOpenEditor(nam) |
754 editor = e5App().getObject("ViewManager").getOpenEditor(nam) |
757 if editor and not editor.checkDirty(): |
755 if editor and not editor.checkDirty(): |
758 return |
756 return |
824 msgPart = "local " |
822 msgPart = "local " |
825 else: |
823 else: |
826 msgPart = "global " |
824 msgPart = "global " |
827 if tagOp in [HgTagDialog.DeleteGlobalTag, HgTagDialog.DeleteLocalTag]: |
825 if tagOp in [HgTagDialog.DeleteGlobalTag, HgTagDialog.DeleteLocalTag]: |
828 args.append('--remove') |
826 args.append('--remove') |
829 if tagOp in [HgTagDialog.CreateGlobalTag, HgTagDialog.CreateLocalTag]: |
827 if ( |
830 if revision: |
828 tagOp in [ |
831 args.append("--rev") |
829 HgTagDialog.CreateGlobalTag, HgTagDialog.CreateLocalTag] and |
832 args.append(revision) |
830 revision |
|
831 ): |
|
832 args.append("--rev") |
|
833 args.append(revision) |
833 if force: |
834 if force: |
834 args.append("--force") |
835 args.append("--force") |
835 args.append('--message') |
836 args.append('--message') |
836 if tagOp in [HgTagDialog.CreateGlobalTag, HgTagDialog.CreateLocalTag]: |
837 if tagOp in [HgTagDialog.CreateGlobalTag, HgTagDialog.CreateLocalTag]: |
837 tag = tag.strip().replace(" ", "_") |
838 tag = tag.strip().replace(" ", "_") |
1076 for line in output.splitlines(): |
1077 for line in output.splitlines(): |
1077 if line and line[0] in "MARC!?I": |
1078 if line and line[0] in "MARC!?I": |
1078 flag, path = line.split(" ", 1) |
1079 flag, path = line.split(" ", 1) |
1079 name = os.path.normcase(os.path.join(repoPath, path)) |
1080 name = os.path.normcase(os.path.join(repoPath, path)) |
1080 dirName = os.path.dirname(name) |
1081 dirName = os.path.dirname(name) |
1081 if name.startswith(dname): |
1082 if name.startswith(dname) and flag not in "?I": |
1082 if flag not in "?I": |
1083 if name in names: |
1083 if name in names: |
1084 names[name] = self.canBeCommitted |
1084 names[name] = self.canBeCommitted |
1085 if dirName in names: |
1085 if dirName in names: |
1086 names[dirName] = self.canBeCommitted |
1086 names[dirName] = self.canBeCommitted |
1087 if dirs: |
1087 if dirs: |
1088 for d in dirs: |
1088 for d in dirs: |
1089 if name.startswith(d): |
1089 if name.startswith(d): |
1090 names[d] = self.canBeCommitted |
1090 names[d] = self.canBeCommitted |
1091 dirs.remove(d) |
1091 dirs.remove(d) |
1092 break |
1092 break |
|
1093 if flag not in "?I": |
1093 if flag not in "?I": |
1094 self.statusCache[name] = self.canBeCommitted |
1094 self.statusCache[name] = self.canBeCommitted |
1095 self.statusCache[dirName] = self.canBeCommitted |
1095 self.statusCache[dirName] = self.canBeCommitted |
1096 else: |
1096 else: |
1097 self.statusCache[name] = self.canBeAdded |
1097 self.statusCache[name] = self.canBeAdded |
1139 entries = [] |
1139 entries = [] |
1140 for pat in patterns: |
1140 for pat in patterns: |
1141 entries.extend(Utilities.direntries(name, True, pat)) |
1141 entries.extend(Utilities.direntries(name, True, pat)) |
1142 |
1142 |
1143 for entry in entries: |
1143 for entry in entries: |
1144 try: |
1144 with contextlib.suppress(OSError): |
1145 os.remove(entry) |
1145 os.remove(entry) |
1146 except OSError: |
|
1147 pass |
|
1148 |
1146 |
1149 def vcsCommandLine(self, name): |
1147 def vcsCommandLine(self, name): |
1150 """ |
1148 """ |
1151 Public method used to execute arbitrary mercurial commands. |
1149 Public method used to execute arbitrary mercurial commands. |
1152 |
1150 |
1212 |
1210 |
1213 output, error = self.__client.runcommand(args) |
1211 output, error = self.__client.runcommand(args) |
1214 |
1212 |
1215 infoBlock = [] |
1213 infoBlock = [] |
1216 if output: |
1214 if output: |
1217 index = 0 |
1215 for index, line in enumerate(output.splitlines(), start=1): |
1218 for line in output.splitlines(): |
|
1219 index += 1 |
|
1220 (changeset, tags, author, date, branches, |
1216 (changeset, tags, author, date, branches, |
1221 bookmarks) = line.split("@@@") |
1217 bookmarks) = line.split("@@@") |
1222 cdate, ctime = date.split()[:2] |
1218 cdate, ctime = date.split()[:2] |
1223 info = [] |
1219 info = [] |
1224 info.append(QCoreApplication.translate( |
1220 info.append(QCoreApplication.translate( |
1246 """<tr><td><b>Last author</b></td><td>{0}</td></tr>\n""" |
1242 """<tr><td><b>Last author</b></td><td>{0}</td></tr>\n""" |
1247 """<tr><td><b>Committed date</b></td><td>{1}</td></tr>\n""" |
1243 """<tr><td><b>Committed date</b></td><td>{1}</td></tr>\n""" |
1248 """<tr><td><b>Committed time</b></td><td>{2}</td></tr>""") |
1244 """<tr><td><b>Committed time</b></td><td>{2}</td></tr>""") |
1249 .format(author, cdate, ctime)) |
1245 .format(author, cdate, ctime)) |
1250 infoBlock.append("\n".join(info)) |
1246 infoBlock.append("\n".join(info)) |
1251 if infoBlock: |
1247 infoStr = ( |
1252 infoStr = """<tr></tr>{0}""".format("<tr></tr>".join(infoBlock)) |
1248 """<tr></tr>{0}""".format("<tr></tr>".join(infoBlock)) |
1253 else: |
1249 if infoBlock else |
1254 infoStr = "" |
1250 "" |
|
1251 ) |
1255 |
1252 |
1256 url = "" |
1253 url = "" |
1257 args = self.initCommand("showconfig") |
1254 args = self.initCommand("showconfig") |
1258 args.append('paths.default') |
1255 args.append('paths.default') |
1259 |
1256 |
1260 output, error = self.__client.runcommand(args) |
1257 output, error = self.__client.runcommand(args) |
1261 |
1258 url = output.splitlines()[0].strip() if output else "" |
1262 if output: |
|
1263 url = output.splitlines()[0].strip() |
|
1264 else: |
|
1265 url = "" |
|
1266 |
1259 |
1267 return QCoreApplication.translate( |
1260 return QCoreApplication.translate( |
1268 'mercurial', |
1261 'mercurial', |
1269 """<h3>Repository information</h3>\n""" |
1262 """<h3>Repository information</h3>\n""" |
1270 """<p><table>\n""" |
1263 """<p><table>\n""" |
1455 |
1448 |
1456 This method gives the chance to enter the revisions to be compared. |
1449 This method gives the chance to enter the revisions to be compared. |
1457 |
1450 |
1458 @param name file/directory name to be diffed (string) |
1451 @param name file/directory name to be diffed (string) |
1459 """ |
1452 """ |
1460 if isinstance(name, list): |
1453 names = name[:] if isinstance(name, list) else [name] |
1461 names = name[:] |
|
1462 else: |
|
1463 names = [name] |
|
1464 for nam in names: |
1454 for nam in names: |
1465 if os.path.isfile(nam): |
1455 if os.path.isfile(nam): |
1466 editor = e5App().getObject("ViewManager").getOpenEditor(nam) |
1456 editor = e5App().getObject("ViewManager").getOpenEditor(nam) |
1467 if editor and not editor.checkDirty(): |
1457 if editor and not editor.checkDirty(): |
1468 return |
1458 return |
1705 '{date|isodate}@@@{branches}@@@{parents}@@@{bookmarks}\n') |
1695 '{date|isodate}@@@{branches}@@@{parents}@@@{bookmarks}\n') |
1706 |
1696 |
1707 output, error = self.__client.runcommand(args) |
1697 output, error = self.__client.runcommand(args) |
1708 |
1698 |
1709 if output: |
1699 if output: |
1710 index = 0 |
1700 for index, line in enumerate(output.splitlines(), start=1): |
1711 for line in output.splitlines(): |
|
1712 index += 1 |
|
1713 (changeset, tags, author, date, branches, parents, |
1701 (changeset, tags, author, date, branches, parents, |
1714 bookmarks) = line.split("@@@") |
1702 bookmarks) = line.split("@@@") |
1715 cdate, ctime = date.split()[:2] |
1703 cdate, ctime = date.split()[:2] |
1716 info.append("""<p><table>""") |
1704 info.append("""<p><table>""") |
1717 if mode == "heads": |
1705 if mode == "heads": |
1791 if isinstance(name, list): |
1779 if isinstance(name, list): |
1792 self.addArguments(args, name) |
1780 self.addArguments(args, name) |
1793 else: |
1781 else: |
1794 args.append(name) |
1782 args.append(name) |
1795 |
1783 |
1796 if unresolve: |
1784 title = ( |
1797 title = self.tr("Marking as 'unresolved'") |
1785 self.tr("Marking as 'unresolved'") |
1798 else: |
1786 if unresolve else |
1799 title = self.tr("Marking as 'resolved'") |
1787 self.tr("Marking as 'resolved'") |
|
1788 ) |
1800 dia = HgDialog(title, self) |
1789 dia = HgDialog(title, self) |
1801 res = dia.startProcess(args) |
1790 res = dia.startProcess(args) |
1802 if res: |
1791 if res: |
1803 dia.exec() |
1792 dia.exec() |
1804 self.checkVCSStatus() |
1793 self.checkVCSStatus() |
1913 defaultUrl, defaultPushUrl = dlg.getData() |
1902 defaultUrl, defaultPushUrl = dlg.getData() |
1914 if withLargefiles: |
1903 if withLargefiles: |
1915 lfMinSize, lfPattern = dlg.getLargefilesData() |
1904 lfMinSize, lfPattern = dlg.getLargefilesData() |
1916 else: |
1905 else: |
1917 createContents = False |
1906 createContents = False |
1918 try: |
1907 with contextlib.suppress(OSError): |
1919 with open(cfgFile, "w") as cfg: |
1908 with open(cfgFile, "w") as cfg: |
1920 if createContents: |
1909 if createContents: |
1921 # write the data entered |
1910 # write the data entered |
1922 cfg.write("[paths]\n") |
1911 cfg.write("[paths]\n") |
1923 if defaultUrl: |
1912 if defaultUrl: |
1936 cfg.write("patterns =\n") |
1925 cfg.write("patterns =\n") |
1937 cfg.write(" {0}\n".format( |
1926 cfg.write(" {0}\n".format( |
1938 "\n ".join(lfPattern))) |
1927 "\n ".join(lfPattern))) |
1939 self.__monitorRepoIniFile(repoName) |
1928 self.__monitorRepoIniFile(repoName) |
1940 self.__iniFileChanged(cfgFile) |
1929 self.__iniFileChanged(cfgFile) |
1941 except OSError: |
|
1942 pass |
|
1943 self.repoEditor = MiniEditor(cfgFile, "Properties") |
1930 self.repoEditor = MiniEditor(cfgFile, "Properties") |
1944 self.repoEditor.show() |
1931 self.repoEditor.show() |
1945 |
1932 |
1946 def hgVerify(self): |
1933 def hgVerify(self): |
1947 """ |
1934 """ |
2031 "glob:__pycache__", |
2018 "glob:__pycache__", |
2032 "glob:**.DS_Store", |
2019 "glob:**.DS_Store", |
2033 ] |
2020 ] |
2034 |
2021 |
2035 ignoreName = os.path.join(name, Hg.IgnoreFileName) |
2022 ignoreName = os.path.join(name, Hg.IgnoreFileName) |
2036 if os.path.exists(ignoreName): |
2023 res = ( |
2037 res = E5MessageBox.yesNo( |
2024 E5MessageBox.yesNo( |
2038 self.__ui, |
2025 self.__ui, |
2039 self.tr("Create .hgignore file"), |
2026 self.tr("Create .hgignore file"), |
2040 self.tr("""<p>The file <b>{0}</b> exists already.""" |
2027 self.tr("""<p>The file <b>{0}</b> exists already.""" |
2041 """ Overwrite it?</p>""").format(ignoreName), |
2028 """ Overwrite it?</p>""").format(ignoreName), |
2042 icon=E5MessageBox.Warning) |
2029 icon=E5MessageBox.Warning) |
2043 else: |
2030 if os.path.exists(ignoreName) else |
2044 res = True |
2031 True |
|
2032 ) |
2045 if res: |
2033 if res: |
2046 try: |
2034 try: |
2047 # create a .hgignore file |
2035 # create a .hgignore file |
2048 with open(ignoreName, "w") as ignore: |
2036 with open(ignoreName, "w") as ignore: |
2049 ignore.write("\n".join(ignorePatterns)) |
2037 ignore.write("\n".join(ignorePatterns)) |
2422 data = dlg.getData() |
2410 data = dlg.getData() |
2423 |
2411 |
2424 if data: |
2412 if data: |
2425 revs, phase, force = data |
2413 revs, phase, force = data |
2426 |
2414 |
|
2415 if phase not in ("p", "d", "s"): |
|
2416 raise ValueError("Invalid phase given.") |
|
2417 |
2427 args = self.initCommand("phase") |
2418 args = self.initCommand("phase") |
2428 if phase == "p": |
2419 if phase == "p": |
2429 args.append("--public") |
2420 args.append("--public") |
2430 elif phase == "d": |
2421 elif phase == "d": |
2431 args.append("--draft") |
2422 args.append("--draft") |
2432 elif phase == "s": |
2423 else: |
2433 args.append("--secret") |
2424 args.append("--secret") |
2434 else: |
2425 |
2435 raise ValueError("Invalid phase given.") |
|
2436 if force: |
2426 if force: |
2437 args.append("--force") |
2427 args.append("--force") |
2438 for rev in revs: |
2428 for rev in revs: |
2439 args.append(rev) |
2429 args.append(rev) |
2440 |
2430 |
3171 @param incoming flag indicating to get incoming bookmarks (boolean) |
3161 @param incoming flag indicating to get incoming bookmarks (boolean) |
3172 @return list of bookmarks (list of string) |
3162 @return list of bookmarks (list of string) |
3173 """ |
3163 """ |
3174 bookmarksList = [] |
3164 bookmarksList = [] |
3175 |
3165 |
3176 if incoming: |
3166 args = ( |
3177 args = self.initCommand("incoming") |
3167 self.initCommand("incoming") |
3178 else: |
3168 if incoming else |
3179 args = self.initCommand("outgoing") |
3169 self.initCommand("outgoing") |
|
3170 ) |
3180 args.append('--bookmarks') |
3171 args.append('--bookmarks') |
3181 |
3172 |
3182 client = self.getClient() |
3173 client = self.getClient() |
3183 output = client.runcommand(args)[0] |
3174 output = client.runcommand(args)[0] |
3184 |
3175 |