public
Last active

  • Download Gist
unsubmodule.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
#!/usr/bin/python2.6
 
# This script "unsubmodulizes" a git repository - i.e. it takes a git
# repository with submodules and replaces the submodules with the
# history of the submodule merged into a subdirectory of the same
# name. I knocked this up quickly as an answer to this stackoverflow
# question:
#
# http://stackoverflow.com/questions/4542729/undo-submodulization-in-git
#
# ... and so it hasn't been tested much: use entirely at your own
# risk, etc. Afterwards, your git repository will have multiple root
# commits.
 
import datetime
import re
import sys
from subprocess import Popen, PIPE, check_call, call
 
# Exit unless the working tree matches the index and the index matches
# HEAD:
 
if 0 != call(["git","diff","--exit-code"]):
print >> sys.stderr, "There are unstaged changes - git status should be clean"
sys.exit(1)
 
if 0 != call(["git","diff","--cached","--exit-code"]):
print >> sys.stderr, "There are changes staged but not committed - git status should be clean"
sys.exit(1)
 
# Find the names of every submodule and the commit that the submodule
# should be at:
 
submodules = []
 
p = Popen(["git","ls-files","--error-unmatch","--stage"],stdout=PIPE)
output = p.communicate()[0]
if p.returncode != 0:
raise Exception, "Finding the submodules failed"
for line in output.decode().splitlines(False):
m = re.search('^160000 ([a-f0-9]{40}) \d+\s+(.*)$',line)
if m:
c, s = m.groups()
if re.search('[:+]',s):
# ':' or '+' in the submodule name will certainly cause
# trouble when we create a remote name based on it, but
# this isn't an exhaustive check (FIXME)
raise Exception, "A remote name created from "+s+" is unlikely to be valid"
submodules.append((s,c))
 
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
 
for submodule, commit in submodules:
print "=== Doing", submodule
# Create temporary names for the remote and branch, taking very
# cursory steps to avoid collisions with existing branches /
# remotes:
remote_name = "submodule-"+submodule+"-"+timestamp
branch_name = "tmp-"+submodule+"-"+timestamp
# Find the URL of the submodule:
p = Popen(["git","config","-f",".gitmodules","--get",'submodule.'+submodule+'.url'],stdout=PIPE)
submodule_url = p.communicate()[0].decode().strip()
if p.returncode != 0:
raise Exception, "Getting the submodule URL failed"
print "Got submodule:", submodule, "at commit", commit, "with URL", submodule_url
# Add a remote for the submodule's URL:
check_call(["git","remote","add",remote_name,submodule_url])
# Fetch all the objects required for the submodule:
check_call(["git","fetch",remote_name])
# Create a temporary branch based on the committed submodule version:
check_call(["git","branch",branch_name,commit])
# Commit removal of the submodule:
check_call(["git","rm","--cached",submodule])
check_call(["git","commit","-m","Removed the submodule "+submodule])
# Move the existing submodule out of the way:
check_call(["mv",submodule,submodule+"."+timestamp])
# Merge in the branch to the subdirectory:
check_call(["git","merge","-s","ours","--no-commit",branch_name])
check_call(["git","read-tree","--prefix="+submodule,"-u",branch_name])
check_call(["git","commit","-m","Merge in "+submodule+" as a subdirectory"])
# Remove the branch and remote that the script created:
check_call(["git","branch","-d",branch_name])
check_call(["git","remote","rm",remote_name])
# Finally, get the subdirectory from the index:
check_call(["git","checkout","--",submodule])

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.