Initial Contribution
diff --git a/import_ext.py b/import_ext.py
new file mode 100644
index 0000000..2a1ebf8
--- /dev/null
+++ b/import_ext.py
@@ -0,0 +1,422 @@
+#
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import random
+import stat
+import sys
+import urllib2
+import StringIO
+
+from error import GitError, ImportError
+from git_command import GitCommand
+
+class ImportExternal(object):
+  """Imports a single revision from a non-git data source.
+     Suitable for use to import a tar or zip based snapshot.
+  """
+  def __init__(self):
+    self._marks = 0
+    self._files = {}
+    self._tempref = 'refs/repo-external/import'
+
+    self._urls = []
+    self._remap = []
+    self.parent = None
+    self._user_name = 'Upstream'
+    self._user_email = 'upstream-import@none'
+    self._user_when = 1000000
+
+    self.commit = None
+
+  def Clone(self):
+    r = self.__class__()
+
+    r.project = self.project
+    for u in self._urls:
+      r._urls.append(u)
+    for p in self._remap:
+      r._remap.append(_PathMap(r, p._old, p._new))
+
+    return r
+
+  def SetProject(self, project):
+    self.project = project
+
+  def SetVersion(self, version):
+    self.version = version
+
+  def AddUrl(self, url):
+    self._urls.append(url)
+
+  def SetParent(self, commit_hash):
+    self.parent = commit_hash
+
+  def SetCommit(self, commit_hash):
+    self.commit = commit_hash
+
+  def RemapPath(self, old, new, replace_version=True):
+    self._remap.append(_PathMap(self, old, new))
+
+  @property
+  def TagName(self):
+    v = ''
+    for c in self.version:
+      if c >= '0' and c <= '9':
+        v += c
+      elif c >= 'A' and c <= 'Z':
+        v += c
+      elif c >= 'a' and c <= 'z':
+        v += c
+      elif c in ('-', '_', '.', '/', '+', '@'):
+        v += c
+    return 'upstream/%s' % v
+
+  @property
+  def PackageName(self):
+    n = self.project.name
+    if n.startswith('platform/'):
+      # This was not my finest moment...
+      #
+      n = n[len('platform/'):]
+    return n
+
+  def Import(self):
+    self._need_graft = False
+    if self.parent:
+      try:
+        self.project.bare_git.cat_file('-e', self.parent)
+      except GitError:
+        self._need_graft = True
+
+    gfi = GitCommand(self.project,
+                     ['fast-import', '--force', '--quiet'],
+                     bare = True,
+                     provide_stdin = True)
+    try:
+      self._out = gfi.stdin
+
+      try:
+        self._UnpackFiles()
+        self._MakeCommit()
+        self._out.flush()
+      finally:
+        rc = gfi.Wait()
+      if rc != 0:
+        raise ImportError('fast-import failed')
+
+      if self._need_graft:
+        id = self._GraftCommit()
+      else:
+        id = self.project.bare_git.rev_parse('%s^0' % self._tempref)
+
+      if self.commit and self.commit != id:
+        raise ImportError('checksum mismatch: %s expected,'
+                          ' %s imported' % (self.commit, id))
+
+      self._MakeTag(id)
+      return id
+    finally:
+      try:
+        self.project.bare_git.DeleteRef(self._tempref)
+      except GitError:
+        pass
+
+  def _PickUrl(self, failed):
+    u = map(lambda x: x.replace('%version%', self.version), self._urls)
+    for f in failed:
+      if f in u:
+        u.remove(f)
+    if len(u) == 0:
+      return None
+    return random.choice(u)
+
+  def _OpenUrl(self):
+    failed = {}
+    while True:
+      url = self._PickUrl(failed.keys())
+      if url is None:
+        why = 'Cannot download %s' % self.project.name
+
+        if failed:
+          why += ': one or more mirrors are down\n'
+          bad_urls = list(failed.keys())
+          bad_urls.sort()
+          for url in bad_urls:
+            why += '  %s: %s\n' % (url, failed[url])
+        else:
+          why += ': no mirror URLs'
+        raise ImportError(why)
+
+      print >>sys.stderr, "Getting %s ..." % url
+      try:
+        return urllib2.urlopen(url), url
+      except urllib2.HTTPError, e:
+        failed[url] = e.code
+      except urllib2.URLError, e:
+        failed[url] = e.reason[1]
+      except OSError, e:
+        failed[url] = e.strerror
+
+  def _UnpackFiles(self):
+    raise NotImplementedError
+
+  def _NextMark(self):
+    self._marks += 1
+    return self._marks
+
+  def _UnpackOneFile(self, mode, size, name, fd):
+    if stat.S_ISDIR(mode):    # directory
+      return
+    else:
+      mode = self._CleanMode(mode, name)
+
+    old_name = name
+    name = self._CleanName(name)
+
+    if stat.S_ISLNK(mode) and self._remap:
+      # The link is relative to the old_name, and may need to
+      # be rewritten according to our remap rules if it goes
+      # up high enough in the tree structure.
+      #
+      dest = self._RewriteLink(fd.read(size), old_name, name)
+      fd = StringIO.StringIO(dest)
+      size = len(dest)
+
+    fi = _File(mode, name, self._NextMark())
+
+    self._out.write('blob\n')
+    self._out.write('mark :%d\n' % fi.mark)
+    self._out.write('data %d\n' % size)
+    while size > 0:
+      n = min(2048, size)
+      self._out.write(fd.read(n))
+      size -= n
+    self._out.write('\n')
+    self._files[fi.name] = fi
+
+  def _SetFileMode(self, name, mode):
+    if not stat.S_ISDIR(mode):
+      mode = self._CleanMode(mode, name)
+      name = self._CleanName(name)
+      try:
+        fi = self._files[name]
+      except KeyError:
+        raise ImportError('file %s was not unpacked' % name)
+      fi.mode = mode
+
+  def _RewriteLink(self, dest, relto_old, relto_new):
+    # Drop the last components of the symlink itself
+    # as the dest is relative to the directory its in.
+    #
+    relto_old = _TrimPath(relto_old)
+    relto_new = _TrimPath(relto_new)
+
+    # Resolve the link to be absolute from the top of
+    # the archive, so we can remap its destination.
+    #
+    while dest.find('/./') >= 0 or dest.find('//') >= 0:
+      dest = dest.replace('/./', '/')
+      dest = dest.replace('//', '/')
+
+    if dest.startswith('../') or dest.find('/../') > 0:
+      dest = _FoldPath('%s/%s' % (relto_old, dest))
+
+    for pm in self._remap:
+      if pm.Matches(dest):
+        dest = pm.Apply(dest)
+        break
+
+    dest, relto_new = _StripCommonPrefix(dest, relto_new)
+    while relto_new:
+      i = relto_new.find('/')
+      if i > 0:
+        relto_new = relto_new[i + 1:]
+      else:
+        relto_new = ''
+      dest = '../' + dest
+    return dest
+
+  def _CleanMode(self, mode, name):
+    if stat.S_ISREG(mode):  # regular file
+      if (mode & 0111) == 0:
+        return 0644
+      else:
+        return 0755
+    elif stat.S_ISLNK(mode):  # symlink
+      return stat.S_IFLNK
+    else:
+      raise ImportError('invalid mode %o in %s' % (mode, name))
+
+  def _CleanName(self, name):
+    old_name = name
+    for pm in self._remap:
+      if pm.Matches(name):
+        name = pm.Apply(name)
+        break
+    while name.startswith('/'):
+      name = name[1:]
+    if not name:
+      raise ImportError('path %s is empty after remap' % old_name)
+    if name.find('/./') >= 0 or name.find('/../') >= 0:
+      raise ImportError('path %s contains relative parts' % name)
+    return name
+
+  def _MakeCommit(self):
+    msg = '%s %s\n' % (self.PackageName, self.version)
+
+    self._out.write('commit %s\n' % self._tempref)
+    self._out.write('committer %s <%s> %d +0000\n' % (
+                    self._user_name,
+                    self._user_email,
+                    self._user_when))
+    self._out.write('data %d\n' % len(msg))
+    self._out.write(msg)
+    self._out.write('\n')
+    if self.parent and not self._need_graft:
+      self._out.write('from %s^0\n' % self.parent)
+      self._out.write('deleteall\n')
+
+    for f in self._files.values():
+      self._out.write('M %o :%d %s\n' % (f.mode, f.mark, f.name))
+    self._out.write('\n')
+
+  def _GraftCommit(self):
+    raw = self.project.bare_git.cat_file('commit', self._tempref)
+    raw = raw.split("\n")
+    while raw[1].startswith('parent '):
+      del raw[1]
+    raw.insert(1, 'parent %s' % self.parent)
+    id = self._WriteObject('commit', "\n".join(raw))
+
+    graft_file = os.path.join(self.project.gitdir, 'info/grafts')
+    if os.path.exists(graft_file):
+      graft_list = open(graft_file, 'rb').read().split("\n")
+      if graft_list and graft_list[-1] == '':
+        del graft_list[-1]
+    else:
+      graft_list = []
+
+    exists = False
+    for line in graft_list:
+      if line == id:
+        exists = True
+        break
+
+    if not exists:
+      graft_list.append(id)
+      graft_list.append('')
+      fd = open(graft_file, 'wb')
+      fd.write("\n".join(graft_list))
+      fd.close()
+
+    return id
+
+  def _MakeTag(self, id):
+    name = self.TagName
+
+    raw = []
+    raw.append('object %s' % id)
+    raw.append('type commit')
+    raw.append('tag %s' % name)
+    raw.append('tagger %s <%s> %d +0000' % (
+      self._user_name,
+      self._user_email,
+      self._user_when))
+    raw.append('')
+    raw.append('%s %s\n' % (self.PackageName, self.version))
+
+    tagid = self._WriteObject('tag', "\n".join(raw))
+    self.project.bare_git.UpdateRef('refs/tags/%s' % name, tagid)
+
+  def _WriteObject(self, type, data):
+    wo = GitCommand(self.project,
+                    ['hash-object', '-t', type, '-w', '--stdin'],
+                    bare = True,
+                    provide_stdin = True,
+                    capture_stdout = True,
+                    capture_stderr = True)
+    wo.stdin.write(data)
+    if wo.Wait() != 0:
+      raise GitError('cannot create %s from (%s)' % (type, data))
+    return wo.stdout[:-1]
+
+
+def _TrimPath(path):
+  i = path.rfind('/')
+  if i > 0:
+    path = path[0:i]
+  return ''
+
+def _StripCommonPrefix(a, b):
+  while True:
+    ai = a.find('/')
+    bi = b.find('/')
+    if ai > 0 and bi > 0 and a[0:ai] == b[0:bi]:
+      a = a[ai + 1:]
+      b = b[bi + 1:]
+    else:
+      break
+  return a, b
+
+def _FoldPath(path):
+  while True:
+    if path.startswith('../'):
+      return path
+
+    i = path.find('/../')
+    if i <= 0:
+      if path.startswith('/'):
+        return path[1:]
+      return path
+
+    lhs = path[0:i]
+    rhs = path[i + 4:]
+
+    i = lhs.rfind('/')
+    if i > 0:
+      path = lhs[0:i + 1] + rhs
+    else:
+      path = rhs
+
+class _File(object):
+  def __init__(self, mode, name, mark):
+    self.mode = mode
+    self.name = name
+    self.mark = mark
+
+
+class _PathMap(object):
+  def __init__(self, imp, old, new):
+    self._imp = imp
+    self._old = old
+    self._new = new
+
+  def _r(self, p):
+    return p.replace('%version%', self._imp.version)
+
+  @property
+  def old(self):
+    return self._r(self._old)
+
+  @property
+  def new(self):
+    return self._r(self._new)
+
+  def Matches(self, name):
+    return name.startswith(self.old)
+
+  def Apply(self, name):
+    return self.new + name[len(self.old):]