aboutsummaryrefslogblamecommitdiffstats
path: root/osc_fast_export.py
blob: 545c8f190c733dc8ccdc9750f5c586a9fecd4fde (plain) (tree)
1
2
3
4
5
6
7
8
9





                                                                                        

              
              
                 
          
                             


                                  


                                                                                        
                             







                                            









                                                                                 











                                                         
                                



                                                                                  


                                                                          














                                                                           
                                






                                                                                    

 
                                                                  

                                                
 
                                

                                     

                                          

                                                      

                                   


                                                                       
                      





















                                                                                   
             





                          
                           

                              
                                                   
                 
#!/usr/bin/python
# https://git-scm.com/book/en/v2/Git-and-Other-Systems-Migrating-to-Git#_custom_importer

import collections
import configparser
from datetime import datetime
import logging
import os.path
import pathlib
import subprocess
import sys
from typing import Dict, List

import xml.etree.ElementTree as ET

logging.basicConfig(format="%(levelname)s:%(funcName)s:%(message)s", level=logging.INFO)
log = logging.getLogger("osc_fast_export")

NULL = open(os.devnull, "wb")

# For reading section-less config files
# https://stackoverflow.com/a/2819788/164233
def FakeSecHead(fp):
    yield "[asection]\n"
    yield from fp


class LogEntry(
    collections.namedtuple("LogEntry", ["rev", "md5", "author", "date", "msg"])
):
    def __str__(self):
        return (
            f"{self.rev}, {self.md5[:12]}, {authors.get(self.author, '<none>')},"
            + f" {datetime.isoformat(self.date)}:\n{self.msg}"
        )


def get_authors() -> Dict[str, str]:
    config = configparser.ConfigParser()
    authors = {}
    authorsfile = pathlib.Path(".osc", "authorsfile.txt")

    if authorsfile.exists():
        config.read_file(FakeSecHead(open(authorsfile)))
        authors = dict(config.items("asection"))

    return authors


def osc_log() -> List[LogEntry]:
    try:
        log_str = subprocess.run(
            ["osc", "log", "--xml"], check=True, text=True, stdout=subprocess.PIPE
        ).stdout
    except subprocess.CalledProcessError as exc:
        raise RuntimeError(f"Cannot collect log of the package!") from exc

    tree = ET.fromstring(log_str)
    log_list = [
        LogEntry(
            int(entry.attrib["revision"]),
            entry.attrib["srcmd5"],
            entry.findtext("author"),
            datetime.strptime(entry.findtext("date"), "%Y-%m-%d %H:%M:%S"),
            entry.findtext("msg"),
        )
        for entry in tree.iter("logentry")
    ]
    log_list.reverse()
    return log_list


def checkout_revision(rev: int):
    try:
        subprocess.check_call(
            ["osc", "up", "-r", f"{rev}"], stdout=NULL, stderr=subprocess.PIPE
        )
        subprocess.check_call(["osc", "clean"], stdout=NULL, stderr=subprocess.PIPE)
    except subprocess.CalledProcessError as exc:
        raise RuntimeError(f"Cannot checkout revision {rev}!") from exc


def print_export(entry: LogEntry, authors: Dict[str, str]) -> int:
    mark = entry.rev
    author = authors.get(entry.author, "<none>")

    checkout_revision(entry.rev)
    print("commit refs/heads/master")
    print(f"mark :{mark}")
    if entry.md5:
        print(f"original-oid {entry.md5}")
    print(f"committer {author} {entry.date:%s} +0000")
    print(f"data {len(entry.msg)}\n{entry.msg}")
    if last_mark:
        print(f"from :{last_mark}")

    # Create actual content of the commit
    # It is easier just to wipe out everything and include files again.
    print("deleteall")
    for dirpath, dirnames, filenames in os.walk("."):
        # TODO are osc notes (aka osc comment) a thing?
        dirpath = os.path.relpath(dirpath, ".")
        if dirpath.startswith(".osc"):
            continue
        # It seems git-fast-export doesn't export directories at all
        # and git-fast-import just creates them when needed.
        # if dirpath != '.':
        #     # create directory
        #     print(f'M 040000 inline {dirpath}')
        for fn in filenames:
            fname = os.path.relpath(os.path.join(dirpath, fn), ".")
            log.debug("dirpath = %s, fname = %s", dirpath, fname)
            fstat = f"{os.stat(fname).st_mode:o}"
            print(f"M {fstat} inline {fname}")
            with open(fname, "rb") as inf:
                dt = inf.read()
                print(f"data {len(dt)}")
                sys.stdout.flush()
                with os.fdopen(sys.stdout.fileno(), "wb", closefd=False) as stdout:
                    stdout.write(dt)
                    stdout.flush()
    print("")

    return mark


if __name__ == "__main__":
    last_mark = None
    authors = get_authors()

    for logentry in osc_log():
        last_mark = print_export(logentry, authors)
    print("done")