ghp_import.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. #! /usr/bin/env python
  2. #
  3. # This file is part of the ghp-import package released under
  4. # the Tumbolia Public License.
  5. # Tumbolia Public License
  6. # Copyright 2013, Paul Davis <paul.joseph.davis@gmail.com>
  7. # Copying and distribution of this file, with or without modification, are
  8. # permitted in any medium without royalty provided the copyright notice and this
  9. # notice are preserved.
  10. # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
  11. # 0. opan saurce LOL
  12. import errno
  13. import logging
  14. import os
  15. import subprocess as sp
  16. import sys
  17. import time
  18. import unicodedata
  19. log = logging.getLogger(__name__)
  20. if sys.version_info[0] == 3:
  21. def enc(text):
  22. if isinstance(text, bytes):
  23. return text
  24. return text.encode()
  25. def dec(text):
  26. if isinstance(text, bytes):
  27. return text.decode('utf-8')
  28. return text
  29. def write(pipe, data):
  30. try:
  31. pipe.stdin.write(data)
  32. except OSError as e:
  33. if e.errno != errno.EPIPE:
  34. raise
  35. else:
  36. def enc(text):
  37. if isinstance(text, unicode): # noqa: F821
  38. return text.encode('utf-8')
  39. return text
  40. def dec(text):
  41. if isinstance(text, unicode): # noqa: F821
  42. return text
  43. return text.decode('utf-8')
  44. def write(pipe, data):
  45. pipe.stdin.write(data)
  46. def normalize_path(path):
  47. # Fix unicode pathnames on OS X
  48. # See: https://stackoverflow.com/a/5582439/44289
  49. if sys.platform == "darwin":
  50. return unicodedata.normalize("NFKC", dec(path))
  51. return path
  52. def try_rebase(remote, branch):
  53. cmd = ['git', 'rev-list', '--max-count=1', '{}/{}'.format(remote, branch)]
  54. p = sp.Popen(cmd, stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE)
  55. (rev, _) = p.communicate()
  56. if p.wait() != 0:
  57. return True
  58. cmd = ['git', 'update-ref', 'refs/heads/%s' % branch, dec(rev.strip())]
  59. if sp.call(cmd) != 0:
  60. return False
  61. return True
  62. def get_config(key):
  63. p = sp.Popen(['git', 'config', key], stdin=sp.PIPE, stdout=sp.PIPE)
  64. (value, _) = p.communicate()
  65. return value.decode('utf-8').strip()
  66. def get_prev_commit(branch):
  67. cmd = ['git', 'rev-list', '--max-count=1', branch, '--']
  68. p = sp.Popen(cmd, stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE)
  69. (rev, _) = p.communicate()
  70. if p.wait() != 0:
  71. return None
  72. return rev.decode('utf-8').strip()
  73. def mk_when(timestamp=None):
  74. if timestamp is None:
  75. timestamp = int(time.time())
  76. currtz = "%+05d" % (-1 * time.timezone / 36) # / 3600 * 100
  77. return "{} {}".format(timestamp, currtz)
  78. def start_commit(pipe, branch, message):
  79. uname = dec(get_config("user.name"))
  80. email = dec(get_config("user.email"))
  81. write(pipe, enc('commit refs/heads/%s\n' % branch))
  82. write(pipe, enc('committer {} <{}> {}\n'.format(uname, email, mk_when())))
  83. write(pipe, enc('data %d\n%s\n' % (len(message), message)))
  84. head = get_prev_commit(branch)
  85. if head:
  86. write(pipe, enc('from %s\n' % head))
  87. write(pipe, enc('deleteall\n'))
  88. def add_file(pipe, srcpath, tgtpath):
  89. with open(srcpath, "rb") as handle:
  90. if os.access(srcpath, os.X_OK):
  91. write(pipe, enc('M 100755 inline %s\n' % tgtpath))
  92. else:
  93. write(pipe, enc('M 100644 inline %s\n' % tgtpath))
  94. data = handle.read()
  95. write(pipe, enc('data %d\n' % len(data)))
  96. write(pipe, enc(data))
  97. write(pipe, enc('\n'))
  98. def add_nojekyll(pipe):
  99. write(pipe, enc('M 100644 inline .nojekyll\n'))
  100. write(pipe, enc('data 0\n'))
  101. write(pipe, enc('\n'))
  102. def gitpath(fname):
  103. norm = os.path.normpath(fname)
  104. return "/".join(norm.split(os.path.sep))
  105. def run_import(srcdir, branch, message, nojekyll):
  106. cmd = ['git', 'fast-import', '--date-format=raw', '--quiet']
  107. kwargs = {"stdin": sp.PIPE}
  108. if sys.version_info >= (3, 2, 0):
  109. kwargs["universal_newlines"] = False
  110. pipe = sp.Popen(cmd, **kwargs)
  111. start_commit(pipe, branch, message)
  112. for path, _, fnames in os.walk(srcdir):
  113. for fn in fnames:
  114. fpath = os.path.join(path, fn)
  115. fpath = normalize_path(fpath)
  116. gpath = gitpath(os.path.relpath(fpath, start=srcdir))
  117. add_file(pipe, fpath, gpath)
  118. if nojekyll:
  119. add_nojekyll(pipe)
  120. write(pipe, enc('\n'))
  121. pipe.stdin.close()
  122. if pipe.wait() != 0:
  123. sys.stdout.write(enc("Failed to process commit.\n"))
  124. def ghp_import(directory, message, remote='origin', branch='gh-pages', force=False):
  125. if not try_rebase(remote, branch):
  126. log.error("Failed to rebase %s branch.", branch)
  127. nojekyll = True
  128. run_import(directory, branch, message, nojekyll)
  129. cmd = ['git', 'push', remote, branch]
  130. if force:
  131. cmd.insert(2, '--force')
  132. proc = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE)
  133. out, err = proc.communicate()
  134. result = proc.wait() == 0
  135. return result, dec(err)