repo: drop nonstandard ConfigObj dependency
[jelmer/dulwich-libgit2.git] / dulwich / patch.py
1 # patch.py -- For dealing wih packed-style patches.
2 # Copryight (C) 2009 Jelmer Vernooij <jelmer@samba.org>
3
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; version 2
7 # of the License or (at your option) a later version.
8
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 # MA  02110-1301, USA.
18
19 """Classes for dealing with git am-style patches.
20
21 These patches are basically unified diffs with some extra metadata tacked 
22 on.
23 """
24
25 import difflib
26 import subprocess
27 import time
28
29
30 def write_commit_patch(f, commit, contents, progress, version=None):
31     """Write a individual file patch.
32
33     :param commit: Commit object
34     :param progress: Tuple with current patch number and total.
35     :return: tuple with filename and contents
36     """
37     (num, total) = progress
38     f.write("From %s %s\n" % (commit.id, time.ctime(commit.commit_time)))
39     f.write("From: %s\n" % commit.author)
40     f.write("Date: %s\n" % time.strftime("%a, %d %b %Y %H:%M:%S %Z"))
41     f.write("Subject: [PATCH %d/%d] %s\n" % (num, total, commit.message))
42     f.write("\n")
43     f.write("---\n")
44     try:
45         p = subprocess.Popen(["diffstat"], stdout=subprocess.PIPE, 
46                              stdin=subprocess.PIPE)
47     except OSError, e:
48         pass # diffstat not available?
49     else:
50         (diffstat, _) = p.communicate(contents)
51         f.write(diffstat)
52         f.write("\n")
53     f.write(contents)
54     f.write("-- \n")
55     if version is None:
56         from dulwich import __version__ as dulwich_version
57         f.write("Dulwich %d.%d.%d\n" % dulwich_version)
58     else:
59         f.write("%s\n" % version)
60
61
62 def get_summary(commit):
63     """Determine the summary line for use in a filename.
64     
65     :param commit: Commit
66     :return: Summary string
67     """
68     return commit.message.splitlines()[0].replace(" ", "-")
69
70
71 def write_blob_diff(f, (old_path, old_mode, old_blob), 
72                        (new_path, new_mode, new_blob)):
73     """Write diff file header.
74
75     :param f: File-like object to write to
76     :param (old_path, old_mode, old_blob): Previous file (None if nonexisting)
77     :param (new_path, new_mode, new_blob): New file (None if nonexisting)
78     """
79     def blob_id(blob):
80         if blob is None:
81             return "0" * 7
82         else:
83             return blob.id[:7]
84     def lines(blob):
85         if blob is not None:
86             return blob.data.splitlines(True)
87         else:
88             return []
89     if old_path is None:
90         old_path = "/dev/null"
91     else:
92         old_path = "a/%s" % old_path
93     if new_path is None:
94         new_path = "/dev/null"
95     else:
96         new_path = "b/%s" % new_path
97     f.write("diff --git %s %s\n" % (old_path, new_path))
98     if old_mode != new_mode:
99         if new_mode is not None:
100             if old_mode is not None:
101                 f.write("old file mode %o\n" % old_mode)
102             f.write("new file mode %o\n" % new_mode) 
103         else:
104             f.write("deleted file mode %o\n" % old_mode)
105     f.write("index %s..%s %o\n" % (
106         blob_id(old_blob), blob_id(new_blob), new_mode))
107     old_contents = lines(old_blob)
108     new_contents = lines(new_blob)
109     f.writelines(difflib.unified_diff(old_contents, new_contents, 
110         old_path, new_path))