A console script that allows you to easily update multiple git repositories at once
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

158 lignes
5.5 KiB

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2011-2014 Ben Kurtovic <ben.kurtovic@gmail.com>
  4. # See the LICENSE file for details.
  5. from __future__ import print_function
  6. import os
  7. from colorama import Fore, Style
  8. from git import Repo, exc
  9. __all__ = ["update_bookmarks", "update_directories"]
  10. BOLD = Style.BRIGHT
  11. RED = Fore.RED + BOLD
  12. GREEN = Fore.GREEN + BOLD
  13. BLUE = Fore.BLUE + BOLD
  14. RESET = Style.RESET_ALL
  15. INDENT1 = " " * 3
  16. INDENT2 = " " * 7
  17. ERROR = RED + "Error:" + RESET
  18. def _read_config(repo, attr):
  19. """Read an attribute from git config."""
  20. try:
  21. return repo.git.config("--get", attr)
  22. except exc.GitCommandError:
  23. return None
  24. def _update_repository(repo, current_only=False, rebase=False, merge=False,
  25. verbose=False):
  26. """Update a single git repository by fetching remotes and rebasing/merging.
  27. The specific actions depend on the arguments given. We will fetch all
  28. remotes if *current_only* is ``False``, or only the remote tracked by the
  29. current branch if ``True``. By default, we will merge unless
  30. ``pull.rebase`` or ``branch.<name>.rebase`` is set in config; *rebase* will
  31. cause us to always rebase with ``--preserve-merges``, and *merge* will
  32. cause us to always merge. If *verbose* is set, additional information is
  33. printed out for the user.
  34. """
  35. def _update_branch(branch):
  36. """Update a single branch."""
  37. print(INDENT2, "Updating", branch, end="...")
  38. upstream = branch.tracking_branch()
  39. if not upstream:
  40. print(" skipped; no upstream is tracked.")
  41. return
  42. if branch.commit == upstream.commit:
  43. print(" up to date.")
  44. return
  45. branch.checkout()
  46. c_attr = "branch.{0}.rebase".format(branch.name)
  47. if not merge and (rebase or repo_rebase or _read_config(repo, c_attr)):
  48. print(" rebasing...", end="")
  49. try:
  50. res = repo.git.rebase(upstream.name)
  51. except exc.GitCommandError as err:
  52. print(err)
  53. ### TODO: ...
  54. else:
  55. print(" done.")
  56. else:
  57. repo.git.merge(upstream.name)
  58. ### TODO: etc
  59. print(INDENT1, BOLD + os.path.split(repo.working_dir)[1] + ":")
  60. active = repo.active_branch
  61. if current_only:
  62. ref = active.tracking_branch()
  63. if not ref:
  64. print(INDENT2, ERROR, "no remote tracked by current branch.")
  65. return
  66. remotes = [repo.remotes[ref.remote_name]]
  67. else:
  68. remotes = repo.remotes
  69. if not remotes:
  70. print(INDENT2, ERROR, "no remotes configured to pull from.")
  71. return
  72. for remote in remotes:
  73. print(INDENT2, "Fetching", remote.name, end="...")
  74. remote.fetch() ### TODO: show progress
  75. print(" done.")
  76. repo_rebase = _read_config(repo, "pull.rebase")
  77. _update_branch(active)
  78. branches = set(repo.heads) - {active}
  79. if branches:
  80. stashed = repo.git.stash("--all") != "No local changes to save" ### TODO: don't do this unless actually necessary
  81. try:
  82. for branch in sorted(branches, key=lambda b: b.name):
  83. _update_branch(branch)
  84. finally:
  85. active.checkout()
  86. if stashed:
  87. repo.git.stash("pop")
  88. def _update_subdirectories(path, long_name, update_args):
  89. """Update all subdirectories that are git repos in a given directory."""
  90. repos = []
  91. for item in os.listdir(path):
  92. try:
  93. repo = Repo(os.path.join(path, item))
  94. except (exc.InvalidGitRepositoryError, exc.NoSuchPathError):
  95. continue
  96. repos.append(repo)
  97. suffix = "ies" if len(repos) != 1 else "y"
  98. print(long_name[0].upper() + long_name[1:],
  99. "contains {0} git repositor{1}:".format(len(repos), suffix))
  100. for repo in sorted(repos, key=lambda r: os.path.split(r.working_dir)[1]):
  101. _update_repository(repo, *update_args)
  102. def _update_directory(path, update_args, is_bookmark=False):
  103. """Update a particular directory.
  104. Determine whether the directory is a git repo on its own, a directory of
  105. git repositories, or something invalid. If the first, update the single
  106. repository; if the second, update all repositories contained within; if the
  107. third, print an error.
  108. """
  109. dir_type = "bookmark" if is_bookmark else "directory"
  110. long_name = dir_type + ' "' + BOLD + path + RESET + '"'
  111. try:
  112. repo = Repo(path)
  113. except exc.NoSuchPathError:
  114. print(ERROR, long_name, "doesn't exist!")
  115. except exc.InvalidGitRepositoryError:
  116. if os.path.isdir(path):
  117. _update_subdirectories(path, long_name, update_args)
  118. else:
  119. print(ERROR, long_name, "isn't a repository!")
  120. else:
  121. long_name = (dir_type.capitalize() + ' "' + BOLD + repo.working_dir +
  122. RESET + '"')
  123. print(long_name, "is a git repository:")
  124. _update_repository(repo, *update_args)
  125. def update_bookmarks(bookmarks, update_args):
  126. """Loop through and update all bookmarks."""
  127. if bookmarks:
  128. for path, name in bookmarks:
  129. _update_directory(path, update_args, is_bookmark=True)
  130. else:
  131. print("You don't have any bookmarks configured! Get help with 'gitup -h'.")
  132. def update_directories(paths, update_args):
  133. """Update a list of directories supplied by command arguments."""
  134. for path in paths:
  135. full_path = os.path.abspath(path)
  136. _update_directory(full_path, update_args, is_bookmark=False)