You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
5.8 KiB

  1. #! /usr/bin/env python3
  2. """Script to synchronize two source trees.
  3. Invoke with two arguments:
  4. python treesync.py slave master
  5. The assumption is that "master" contains CVS administration while
  6. slave doesn't. All files in the slave tree that have a CVS/Entries
  7. entry in the master tree are synchronized. This means:
  8. If the files differ:
  9. if the slave file is newer:
  10. normalize the slave file
  11. if the files still differ:
  12. copy the slave to the master
  13. else (the master is newer):
  14. copy the master to the slave
  15. normalizing the slave means replacing CRLF with LF when the master
  16. doesn't use CRLF
  17. """
  18. import os, sys, stat, getopt
  19. # Interactivity options
  20. default_answer = "ask"
  21. create_files = "yes"
  22. create_directories = "no"
  23. write_slave = "ask"
  24. write_master = "ask"
  25. def main():
  26. global always_no, always_yes
  27. global create_directories, write_master, write_slave
  28. opts, args = getopt.getopt(sys.argv[1:], "nym:s:d:f:a:")
  29. for o, a in opts:
  30. if o == '-y':
  31. default_answer = "yes"
  32. if o == '-n':
  33. default_answer = "no"
  34. if o == '-s':
  35. write_slave = a
  36. if o == '-m':
  37. write_master = a
  38. if o == '-d':
  39. create_directories = a
  40. if o == '-f':
  41. create_files = a
  42. if o == '-a':
  43. create_files = create_directories = write_slave = write_master = a
  44. try:
  45. [slave, master] = args
  46. except ValueError:
  47. print("usage: python", sys.argv[0] or "treesync.py", end=' ')
  48. print("[-n] [-y] [-m y|n|a] [-s y|n|a] [-d y|n|a] [-f n|y|a]", end=' ')
  49. print("slavedir masterdir")
  50. return
  51. process(slave, master)
  52. def process(slave, master):
  53. cvsdir = os.path.join(master, "CVS")
  54. if not os.path.isdir(cvsdir):
  55. print("skipping master subdirectory", master)
  56. print("-- not under CVS")
  57. return
  58. print("-"*40)
  59. print("slave ", slave)
  60. print("master", master)
  61. if not os.path.isdir(slave):
  62. if not okay("create slave directory %s?" % slave,
  63. answer=create_directories):
  64. print("skipping master subdirectory", master)
  65. print("-- no corresponding slave", slave)
  66. return
  67. print("creating slave directory", slave)
  68. try:
  69. os.mkdir(slave)
  70. except os.error as msg:
  71. print("can't make slave directory", slave, ":", msg)
  72. return
  73. else:
  74. print("made slave directory", slave)
  75. cvsdir = None
  76. subdirs = []
  77. names = os.listdir(master)
  78. for name in names:
  79. mastername = os.path.join(master, name)
  80. slavename = os.path.join(slave, name)
  81. if name == "CVS":
  82. cvsdir = mastername
  83. else:
  84. if os.path.isdir(mastername) and not os.path.islink(mastername):
  85. subdirs.append((slavename, mastername))
  86. if cvsdir:
  87. entries = os.path.join(cvsdir, "Entries")
  88. for e in open(entries).readlines():
  89. words = e.split('/')
  90. if words[0] == '' and words[1:]:
  91. name = words[1]
  92. s = os.path.join(slave, name)
  93. m = os.path.join(master, name)
  94. compare(s, m)
  95. for (s, m) in subdirs:
  96. process(s, m)
  97. def compare(slave, master):
  98. try:
  99. sf = open(slave, 'r')
  100. except IOError:
  101. sf = None
  102. try:
  103. mf = open(master, 'rb')
  104. except IOError:
  105. mf = None
  106. if not sf:
  107. if not mf:
  108. print("Neither master nor slave exists", master)
  109. return
  110. print("Creating missing slave", slave)
  111. copy(master, slave, answer=create_files)
  112. return
  113. if not mf:
  114. print("Not updating missing master", master)
  115. return
  116. if sf and mf:
  117. if identical(sf, mf):
  118. return
  119. sft = mtime(sf)
  120. mft = mtime(mf)
  121. if mft > sft:
  122. # Master is newer -- copy master to slave
  123. sf.close()
  124. mf.close()
  125. print("Master ", master)
  126. print("is newer than slave", slave)
  127. copy(master, slave, answer=write_slave)
  128. return
  129. # Slave is newer -- copy slave to master
  130. print("Slave is", sft-mft, "seconds newer than master")
  131. # But first check what to do about CRLF
  132. mf.seek(0)
  133. fun = funnychars(mf)
  134. mf.close()
  135. sf.close()
  136. if fun:
  137. print("***UPDATING MASTER (BINARY COPY)***")
  138. copy(slave, master, "rb", answer=write_master)
  139. else:
  140. print("***UPDATING MASTER***")
  141. copy(slave, master, "r", answer=write_master)
  142. BUFSIZE = 16*1024
  143. def identical(sf, mf):
  144. while 1:
  145. sd = sf.read(BUFSIZE)
  146. md = mf.read(BUFSIZE)
  147. if sd != md: return 0
  148. if not sd: break
  149. return 1
  150. def mtime(f):
  151. st = os.fstat(f.fileno())
  152. return st[stat.ST_MTIME]
  153. def funnychars(f):
  154. while 1:
  155. buf = f.read(BUFSIZE)
  156. if not buf: break
  157. if '\r' in buf or '\0' in buf: return 1
  158. return 0
  159. def copy(src, dst, rmode="rb", wmode="wb", answer='ask'):
  160. print("copying", src)
  161. print(" to", dst)
  162. if not okay("okay to copy? ", answer):
  163. return
  164. f = open(src, rmode)
  165. g = open(dst, wmode)
  166. while 1:
  167. buf = f.read(BUFSIZE)
  168. if not buf: break
  169. g.write(buf)
  170. f.close()
  171. g.close()
  172. def raw_input(prompt):
  173. sys.stdout.write(prompt)
  174. sys.stdout.flush()
  175. return sys.stdin.readline()
  176. def okay(prompt, answer='ask'):
  177. answer = answer.strip().lower()
  178. if not answer or answer[0] not in 'ny':
  179. answer = input(prompt)
  180. answer = answer.strip().lower()
  181. if not answer:
  182. answer = default_answer
  183. if answer[:1] == 'y':
  184. return 1
  185. if answer[:1] == 'n':
  186. return 0
  187. print("Yes or No please -- try again:")
  188. return okay(prompt)
  189. if __name__ == '__main__':
  190. main()