A console script that allows you to easily update multiple git repositories at once
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

173 satır
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)