1#!/usr/bin/env python2
2# SPDX-License-Identifier: GPL-2.0+
3#
4# Author: Masahiro Yamada <yamada.m@jp.panasonic.com>
5#
6
7"""
8Fill the "Commit" and "Removed" fields of doc/README.scrapyard
9
10The file doc/README.scrapyard is used to keep track of removed boards.
11
12When we remove support for boards, we are supposed to add entries to
13doc/README.scrapyard leaving "Commit" and "Removed" fields blank.
14
15The "Commit" field is the commit hash in which the board was removed
16and the "Removed" is the date at which the board was removed.  Those
17two are known only after the board removal patch was applied, thus they
18need to be filled in later.
19
20This effectively means that the person who removes other boards is
21supposed to fill in the blank fields before adding new entries to
22doc/README.scrapyard.
23
24That is a really tedious task that should be automated.
25This script fills the blank fields of doc/README.scrapyard for you!
26
27Usage:
28
29The "Commit" and "Removed" fields must be "-".  The other fields should
30have already been filled in by a former commit.
31
32Run
33    scripts/fill_scrapyard.py
34"""
35
36import os
37import subprocess
38import sys
39import tempfile
40
41DOC='doc/README.scrapyard'
42
43def get_last_modify_commit(file, line_num):
44    """Get the commit that last modified the given line.
45
46    This function runs "git blame" against the given line of the given
47    file and returns the commit hash that last modified it.
48
49    Arguments:
50      file: the file to be git-blame'd.
51      line_num: the line number to be git-blame'd.  This line number
52                starts from 1, not 0.
53
54    Returns:
55      Commit hash that last modified the line.  The number of digits is
56      long enough to form a unique commit.
57    """
58    result = subprocess.check_output(['git', 'blame', '-L',
59                                      '%d,%d' % (line_num, line_num), file])
60    commit = result.split()[0]
61
62    if commit[0] == '^':
63        sys.exit('%s: line %d: ' % (file, line_num) +
64                 'this line was modified before the beginning of git history')
65
66    if commit == '0' * len(commit):
67        sys.exit('%s: line %d: locally modified\n' % (file, line_num) +
68                 'Please run this script in a clean repository.')
69
70    return commit
71
72def get_committer_date(commit):
73    """Get the committer date of the given commit.
74
75    This function returns the date when the given commit was applied.
76
77    Arguments:
78      commit: commit-ish object.
79
80    Returns:
81      The committer date of the given commit in the form YY-MM-DD.
82    """
83    committer_date = subprocess.check_output(['git', 'show', '-s',
84                                              '--format=%ci', commit])
85    return committer_date.split()[0]
86
87def move_to_topdir():
88    """Change directory to the top of the git repository.
89
90    Or, exit with an error message if called out of a git repository.
91    """
92    try:
93        toplevel = subprocess.check_output(['git', 'rev-parse',
94                                            '--show-toplevel'])
95    except subprocess.CalledProcessError:
96        sys.exit('Please run in a git repository.')
97
98    # strip '\n'
99    toplevel = toplevel.rstrip()
100
101    # Change the current working directory to the toplevel of the respository
102    # for our easier life.
103    os.chdir(toplevel)
104
105class TmpFile:
106
107    """Useful class to handle a temporary file.
108
109    tempfile.mkstemp() is often used to create a unique temporary file,
110    but what is inconvenient is that the caller is responsible for
111    deleting the file when done with it.
112
113    Even when the caller errors out on the way, the temporary file must
114    be deleted somehow.  The idea here is that we delete the file in
115    the destructor of this class because the destructor is always
116    invoked when the instance of the class is freed.
117    """
118
119    def __init__(self):
120        """Constructor - create a temporary file"""
121        fd, self.filename = tempfile.mkstemp()
122        self.file = os.fdopen(fd, 'w')
123
124    def __del__(self):
125        """Destructor - delete the temporary file"""
126        try:
127            os.remove(self.filename)
128        except:
129            pass
130
131def main():
132    move_to_topdir()
133
134    line_num = 1
135
136    tmpfile = TmpFile()
137    for line in open(DOC):
138        tmp = line.split(None, 5)
139        modified = False
140
141        if len(tmp) >= 5:
142            # fill "Commit" field
143            if tmp[3] == '-':
144                tmp[3] = get_last_modify_commit(DOC, line_num)
145                modified = True
146            # fill "Removed" field
147            if tmp[4] == '-':
148                tmp[4] = get_committer_date(tmp[3])
149            if modified:
150                line  = tmp[0].ljust(17)
151                line += tmp[1].ljust(12)
152                line += tmp[2].ljust(15)
153                line += tmp[3].ljust(12)
154                line += tmp[4].ljust(12)
155                if len(tmp) >= 6:
156                    line += tmp[5]
157                line = line.rstrip() + '\n'
158
159        tmpfile.file.write(line)
160        line_num += 1
161
162    os.rename(tmpfile.filename, DOC)
163
164if __name__ == '__main__':
165    main()
166