Allow the property name for CVS descriptions to be chosen by the user.
[cvs2svn.git] / contrib / git-move-tags.py
blob1c7f2dc9054a0e954acaad8faee2a0127109f3f4
1 #!/usr/bin/python
3 """Remove redundant tag fixup commits from a cvs2svn-converted git repository.
5 Process each tag in a git repository. If the tagged commit is
6 tree-wise identical with another tagged commit, the tag is moved to
7 point at the other commit (i.e., tags pointing at identical content
8 will all point at a single tag fixup commit).
10 Furthermore, if one of the parents of the tag fixup commit is
11 identical to the tag fixup commit itself, then the tag is moved to the
12 parent.
14 The script is meant to be run against a repository converted by
15 cvs2svn, since cvs2svn creates empty commits for some tags.
17 """
19 from subprocess import Popen, PIPE, call
21 # Cache trees we have already seen, and that are suitable targets for
22 # moved tags
23 tree_cache = {} # tree SHA1 -> commit SHA1
25 # Cache parent commit -> parent tree mapping
26 parent_cache = {} # commit SHA1 -> tree SHA1
28 def resolve_commit(commit):
29 """Return the tree object associated with the given commit."""
31 get_tree_cmd = ["git", "rev-parse", commit + "^{tree}"]
32 tree = Popen(get_tree_cmd, stdout = PIPE).communicate()[0].strip()
33 return tree
35 def move_tag(tag, from_commit, to_commit):
36 """Move the given tag to the given commit."""
38 print "Moving tag %s from %s to %s..." % (tag, from_commit, to_commit),
39 retcode = call(["git", "tag", "-f", tag, to_commit])
40 if retcode == 0:
41 print "done"
42 else:
43 print "FAILED"
45 def try_to_move_tag(tag, commit, tree, parents):
46 """Try to move the given tag to a separate commit (with identical tree)."""
48 if tree in tree_cache:
49 # We have already found a suitable commit for this tree
50 move_tag(tag, commit, tree_cache[tree])
51 return
53 # Try to move this tag to one of its commit's parents
54 for p in parents:
55 if p not in parent_cache:
56 # Not in cache
57 parent_cache[p] = resolve_commit(p)
58 p_tree = parent_cache[p]
59 if tree == p_tree:
60 # We can move tag to parent p
61 move_tag(tag, commit, p)
62 commit = p
63 break
65 # Register the resulting commit object in the tree_cache
66 assert tree not in tree_cache # Sanity check
67 tree_cache[tree] = commit
69 # Command for retrieving tags and associated metadata
70 # See 'git for-each-ref' manual page for --format details
71 get_tag_info_cmd = [
72 "git",
73 "for-each-ref",
74 "--format=%(refname)%00%(objecttype)%00%(subject)%00"
75 "%(objectname)%00%(tree)%00%(parent)%00"
76 "%(*objectname)%00%(*tree)%00%(*parent)",
77 "refs/tags",
80 get_tag_info = Popen(get_tag_info_cmd, stdout = PIPE)
82 while True: # While get_tag_info process is still running
83 for line in get_tag_info.stdout:
84 line = line.strip()
85 (tag, objtype, subject,
86 commit, tree, parents,
87 commit_alt, tree_alt, parents_alt) = line.split(chr(0))
88 if objtype == "tag":
89 commit = commit_alt
90 tree = tree_alt
91 parents = parents_alt
92 elif objtype != "commit":
93 continue
95 if subject.startswith("This commit was manufactured by cvs2svn") \
96 or not subject:
97 # We shall try to move this tag, if possible
98 parent_list = []
99 if parents:
100 parent_list = parents.split(" ")
101 for p in parent_list:
102 assert len(p) == 40
103 assert tag.startswith("refs/tags/")
104 try_to_move_tag(tag[10:], commit, tree, parent_list)
105 else:
106 # We shall not move this tag, but it is a possible target
107 # for other tags that we _do_ want to move
108 tree_cache.setdefault(tree, commit)
110 if get_tag_info.poll() is not None:
111 # Break if no longer running:
112 break
114 assert get_tag_info.returncode == 0