Re: contribution: git-move-heads.py
Steve Folly <[email protected]>
| Newsgroups | gmane.comp.version-control.subversion.cvs2svn.user |
|---|---|
| Message-ID | <[email protected]> |
Hi, Attached is a script for cvs2git that combines moving tags and head references. Give the options --tags and/or --heads depending on which refs you want to move. ------------------------------------------------------ http://cvs2svn.tigris.org/ds/viewMessage.do?dsForumId=1670&dsMessageId=2451727 To unsubscribe from this discussion, e-mail: [[email protected]]. Feel free to do with it as you see fit. :-) Regards, Steve Folly On 15 Feb 2010, at 03:24, Michael Haggerty wrote: > Steve Folly wrote: >> I hope you find this useful - I took the git-move-tags.py script and >> modified it to do the same thing for branches (heads). >> >> I have many branches in my CVS repository, and because I'm now >> splitting it up into multiple git repositories, sometimes there are >> branches with no commits on them. I realised I could take the >> git-move-tags.py script and apply the same principles to heads. I >> know moving heads isn't so important as moving tags, since you'll >> naturally get a branch in tree on it's first commit anyway, but >> nevertheless it makes the commit tree more aesthetically pleasing to >> look at :-) > > Sounds reasonable. > >> I wasn't sure to whether to munge them together or not, so here's a >> separate git-move-heads.py to start off with. > > There's no need to be shy about suggesting changes to existing code :-) > > Given that the two scripts are quite similar, it seems like it would > make sense to combine them. If you think that people might want to move > tags but not branches or vice versa, then the script could have > command-line options telling it what to do. What do you think? > > Michael
git-move-refs.py
(text/x-python-script, 4.6 KB)
#!/usr/bin/python
"""Remove redundant fixup commits from a cvs2svn-converted git repository.
Process each head ref and tag in a git repository. If the associated commit
is tree-wise identical with another commit, the head or tag is moved to point
at the other commit (i.e., refs pointing at identical content will all point
at a single fixup commit).
Furthermore, if one of the parents of the fixup commit is identical to the
fixup commit itself, then the head or tag is moved to the parent.
The script is meant to be run against a repository converted by cvs2svn,
since cvs2svn creates empty commits for some tags and head refs (branches).
"""
import sys
from subprocess import Popen, PIPE, call
# Cache trees we have already seen, and that are suitable targets for
# moved refs
tree_cache = {} # tree SHA1 -> commit SHA1
# Cache parent commit -> parent tree mapping
parent_cache = {} # commit SHA1 -> tree SHA1
def resolve_commit(commit):
"""Return the tree object associated with the given commit."""
get_tree_cmd = ["git", "rev-parse", commit + "^{tree}"]
tree = Popen(get_tree_cmd, stdout = PIPE).communicate()[0].strip()
return tree
def move_ref(ref, from_commit, to_commit, ref_type):
"""Move the given head to the given commit.
ref_type is either "tags" or "heads"
"""
if from_commit != to_commit:
print "Moving ref %s from %s to %s..." % (ref, from_commit, to_commit),
command = "tag" if ref_type == "tags" else "branch"
retcode = call(["git", command, "-f", ref, to_commit])
if retcode == 0:
print "done"
else:
print "FAILED"
def try_to_move_ref(ref, commit, tree, parents, ref_type):
"""Try to move the given ref to a separate commit (with identical tree)."""
if tree in tree_cache:
# We have already found a suitable commit for this tree
move_ref(ref, commit, tree_cache[tree], ref_type)
return
# Try to move this ref to one of its commit's parents
for p in parents:
if p not in parent_cache:
# Not in cache
parent_cache[p] = resolve_commit(p)
p_tree = parent_cache[p]
if tree == p_tree:
# We can move ref to parent p
move_ref(ref, commit, p, ref)
commit = p
break
# Register the resulting commit object in the tree_cache
assert tree not in tree_cache # Sanity check
tree_cache[tree] = commit
def process_refs(ref_type):
tree_cache = {}
parent_cache = {}
# Command for retrieving refs and associated metadata
# See 'git for-each-ref' manual page for --format details
get_ref_info_cmd = [
"git",
"for-each-ref",
"--format=%(refname)%00%(objecttype)%00%(subject)%00"
"%(objectname)%00%(tree)%00%(parent)%00"
"%(*objectname)%00%(*tree)%00%(*parent)",
"refs/%s" % (ref_type,),
]
get_ref_info = Popen(get_ref_info_cmd, stdout = PIPE)
while True: # While get_ref_info process is still running
for line in get_ref_info.stdout:
line = line.strip()
(ref, objtype, subject,
commit, tree, parents,
commit_alt, tree_alt, parents_alt) = line.split(chr(0))
if objtype == "tag":
commit = commit_alt
tree = tree_alt
parents = parents_alt
elif objtype != "commit":
continue
if subject.startswith("This commit was manufactured by cvs2svn") \
or not subject:
# We shall try to move this ref, if possible
parent_list = []
if parents:
parent_list = parents.split(" ")
for p in parent_list:
assert len(p) == 40
ref_prefix = "refs/%s/" % (ref_type,)
assert ref.startswith(ref_prefix)
try_to_move_ref(ref[len(ref_prefix):], commit, tree, parent_list, ref_type)
else:
# We shall not move this ref, but it is a possible target
# for other refs that we _do_ want to move
tree_cache.setdefault(tree, commit)
if get_ref_info.poll() is not None:
# Break if no longer running:
break
assert get_ref_info.returncode == 0
do_tags = False
do_heads = False
for argv in sys.argv[1:]:
if argv == '--tags' or argv == '-t':
do_tags = True
elif argv == '--heads'or argv == '-h':
do_heads = True
if do_tags:
process_refs("tags")
if do_heads:
process_refs("heads")