A console script that allows you to easily update multiple git repositories at once
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

173 linhas
6.1 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:", branch, end=" ")
  38. upstream = branch.tracking_branch()
  39. if not upstream:
  40. print("Branch is not tracking any remote.")
  41. continue
  42. c_attr = "branch.{0}.rebase".format(branch.name)
  43. if not merge and (rebase or repo_rebase or _read_config(repo, c_attr)):
  44. ### TODO: rebase
  45. else:
  46. ### TODO: merge
  47. print(INDENT1, BOLD + os.path.split(repo.working_dir)[1] + ":")
  48. active = repo.active_branch
  49. if current_only:
  50. ref = active.tracking_branch()
  51. if not ref:
  52. print(INDENT2, ERROR, "no remote tracked by current branch.")
  53. return
  54. remotes = [repo.remotes[ref.remote_name]]
  55. else:
  56. remotes = repo.remotes
  57. for remote in remotes:
  58. print(INDENT2, "Fetching remote:", remote.name)
  59. remote.fetch() # TODO: show progress
  60. repo_rebase = _read_config(repo, "pull.rebase")
  61. _update_branch(active)
  62. branches = set(repo.heads) - {active}
  63. if branches:
  64. stashed = repo.git.stash("--all") != "No local changes to save"
  65. try:
  66. for branch in sorted(branches, key=lambda b: b.name):
  67. branch.checkout()
  68. _update_branch(branch)
  69. finally:
  70. active.checkout()
  71. if stashed:
  72. repo.git.stash("pop")
  73. #####################################
  74. try:
  75. last_commit = _exec_shell("git log -n 1 --pretty=\"%ar\"")
  76. except subprocess.CalledProcessError:
  77. last_commit = "never" # Couldn't get a log, so no commits
  78. if not dry_fetch: # No new changes to pull
  79. print(INDENT2, BLUE + "No new changes." + RESET,
  80. "Last commit was {0}.".format(last_commit))
  81. else: # Stuff has happened!
  82. print(INDENT2, "There are new changes upstream...")
  83. status = _exec_shell("git status")
  84. if status.endswith("nothing to commit, working directory clean"):
  85. print(INDENT2, GREEN + "Pulling new changes...")
  86. result = _exec_shell("git pull")
  87. if last_commit == "never":
  88. print(INDENT2, "The following changes have been made:")
  89. else:
  90. print(INDENT2, "The following changes have been made since",
  91. last_commit + ":")
  92. print(result)
  93. else:
  94. print(INDENT2, RED + "Warning:" + RESET,
  95. "you have uncommitted changes in this repository!")
  96. print(INDENT2, "Ignoring.")
  97. def _update_subdirectories(path, long_name, update_args):
  98. """Update all subdirectories that are git repos in a given directory."""
  99. repos = []
  100. for item in os.listdir(path):
  101. try:
  102. repo = Repo(os.path.join(path, item))
  103. except (exc.InvalidGitRepositoryError, exc.NoSuchPathError):
  104. continue
  105. repos.append(repo)
  106. suffix = "ies" if len(repos) != 1 else "y"
  107. print(long_name[0].upper() + long_name[1:],
  108. "contains {0} git repositor{1}:".format(len(repos), suffix))
  109. for repo in sorted(repos, key=lambda r: os.path.split(r.working_dir)[1]):
  110. _update_repository(repo, *update_args)
  111. def _update_directory(path, update_args, is_bookmark=False):
  112. """Update a particular directory.
  113. Determine whether the directory is a git repo on its own, a directory of
  114. git repositories, or something invalid. If the first, update the single
  115. repository; if the second, update all repositories contained within; if the
  116. third, print an error.
  117. """
  118. dir_type = "bookmark" if is_bookmark else "directory"
  119. long_name = dir_type + ' "' + BOLD + path + RESET + '"'
  120. try:
  121. repo = Repo(path)
  122. except exc.NoSuchPathError:
  123. print(ERROR, long_name, "doesn't exist!")
  124. except exc.InvalidGitRepositoryError:
  125. if os.path.isdir(path):
  126. _update_subdirectories(path, long_name, update_args)
  127. else:
  128. print(ERROR, long_name, "isn't a repository!")
  129. else:
  130. long_name = (dir_type.capitalize() + ' "' + BOLD + repo.working_dir +
  131. RESET + '"')
  132. print(long_name, "is a git repository:")
  133. _update_repository(repo, *update_args)
  134. def update_bookmarks(bookmarks, update_args):
  135. """Loop through and update all bookmarks."""
  136. if bookmarks:
  137. for path, name in bookmarks:
  138. _update_directory(path, update_args, is_bookmark=True)
  139. else:
  140. print("You don't have any bookmarks configured! Get help with 'gitup -h'.")
  141. def update_directories(paths, update_args):
  142. """Update a list of directories supplied by command arguments."""
  143. for path in paths:
  144. full_path = os.path.abspath(path)
  145. _update_directory(full_path, update_args, is_bookmark=False)