# reflog.py -- Parsing and writing reflog files # Copyright (C) 2015 Jelmer Vernooij and others. # # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU # General Public License as public by the Free Software Foundation; version 2.0 # or (at your option) any later version. You can redistribute it and/or # modify it under the terms of either of these two licenses. # # 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. # # You should have received a copy of the licenses; if not, see # for a copy of the GNU General Public License # and for a copy of the Apache # License, Version 2.0. # """Utilities for reading and generating reflogs.""" import collections from .objects import ZERO_SHA, format_timezone, parse_timezone Entry = collections.namedtuple( "Entry", ["old_sha", "new_sha", "committer", "timestamp", "timezone", "message"], ) def format_reflog_line(old_sha, new_sha, committer, timestamp, timezone, message): """Generate a single reflog line. Args: old_sha: Old Commit SHA new_sha: New Commit SHA committer: Committer name and e-mail timestamp: Timestamp timezone: Timezone message: Message """ if old_sha is None: old_sha = ZERO_SHA return ( old_sha + b" " + new_sha + b" " + committer + b" " + str(int(timestamp)).encode("ascii") + b" " + format_timezone(timezone) + b"\t" + message ) def parse_reflog_line(line): """Parse a reflog line. Args: line: Line to parse Returns: Tuple of (old_sha, new_sha, committer, timestamp, timezone, message) """ (begin, message) = line.split(b"\t", 1) (old_sha, new_sha, rest) = begin.split(b" ", 2) (committer, timestamp_str, timezone_str) = rest.rsplit(b" ", 2) return Entry( old_sha, new_sha, committer, int(timestamp_str), parse_timezone(timezone_str)[0], message, ) def read_reflog(f): """Read reflog. Args: f: File-like object Returns: Iterator over Entry objects """ for line in f: yield parse_reflog_line(line) def drop_reflog_entry(f, index, rewrite=False): """Drop the specified reflog entry. Args: f: File-like object index: Reflog entry index (in Git reflog reverse 0-indexed order) rewrite: If a reflog entry's predecessor is removed, set its old SHA to the new SHA of the entry that now precedes it """ if index < 0: raise ValueError("Invalid reflog index %d" % index) log = [] offset = f.tell() for line in f: log.append((offset, parse_reflog_line(line))) offset = f.tell() inverse_index = len(log) - index - 1 write_offset = log[inverse_index][0] f.seek(write_offset) if index == 0: f.truncate() return del log[inverse_index] if rewrite and index > 0 and log: if inverse_index == 0: previous_new = ZERO_SHA else: previous_new = log[inverse_index - 1][1].new_sha offset, entry = log[inverse_index] log[inverse_index] = ( offset, Entry( previous_new, entry.new_sha, entry.committer, entry.timestamp, entry.timezone, entry.message, ), ) for _, entry in log[inverse_index:]: f.write( format_reflog_line( entry.old_sha, entry.new_sha, entry.committer, entry.timestamp, entry.timezone, entry.message, ) ) f.truncate()