1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
# Copyright (C) 2014 Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import cliapp
import logging
import os
import re
import urlparse
import morphlib
class AddBinaryPlugin(cliapp.Plugin):
'''Add a subcommand for dealing with large binary files.'''
def enable(self):
self.app.add_subcommand(
'add-binary', self.add_binary, arg_synopsis='FILENAME...')
def disable(self):
pass
def add_binary(self, binaries):
'''Add a binary file to the current repository.
Command line argument:
* `FILENAME...` is the binaries to be added to the repository.
This checks for the existence of a .gitfat file in the repository. If
there is one then a line is added to .gitattributes telling it that
the given binary should be handled by git-fat. If there is no .gitfat
file then it is created, with the rsync remote pointing at the correct
directory on the Trove host. A line is then added to .gitattributes to
say that the given binary should be handled by git-fat.
Example:
morph add-binary big_binary.tar.gz
'''
if not binaries:
raise morphlib.Error('add-binary must get at least one argument')
gd = morphlib.gitdir.GitDirectory(os.getcwd())
gd.fat_init()
if not gd.has_fat():
self._make_gitfat(gd)
self._handle_binaries(binaries, gd)
logging.info('Staged binaries for commit')
def _handle_binaries(self, binaries, gd):
'''Add a filter for the given file, and then add it to the repo.'''
# begin by ensuring all paths given are relative to the root directory
files = [gd.get_relpath(os.path.realpath(binary))
for binary in binaries]
# escape special characters and whitespace
escaped = []
for path in files:
path = self.escape_glob(path)
path = self.escape_whitespace(path)
escaped.append(path)
# now add any files that aren't already mentioned in .gitattributes to
# the file so that git fat knows what to do
attr_path = gd.join_path('.gitattributes')
if '.gitattributes' in gd.list_files():
with open(attr_path, 'r') as attributes:
current = set(f.split()[0] for f in attributes)
else:
current = set()
to_add = set(escaped) - current
# if we don't need to change .gitattributes then we can just do
# `git add <binaries>`
if not to_add:
gd.get_index().add_files_from_working_tree(files)
return
with open(attr_path, 'a') as attributes:
for path in to_add:
attributes.write('%s filter=fat -crlf\n' % path)
# we changed .gitattributes, so need to stage it for committing
files.append(attr_path)
gd.get_index().add_files_from_working_tree(files)
def _make_gitfat(self, gd):
'''Make .gitfat point to the rsync directory for the repo.'''
remote = gd.get_remote('origin')
if not remote.get_push_url():
raise Exception(
'Remote `origin` does not have a push URL defined.')
url = urlparse.urlparse(remote.get_push_url())
if url.scheme != 'ssh':
raise Exception(
'Push URL for `origin` is not an SSH URL: %s' % url.geturl())
fat_store = '%s:%s' % (url.netloc, url.path)
fat_path = gd.join_path('.gitfat')
with open(fat_path, 'w+') as gitfat:
gitfat.write('[rsync]\n')
gitfat.write('remote = %s' % fat_store)
gd.get_index().add_files_from_working_tree([fat_path])
def escape_glob(self, path):
'''Escape glob metacharacters in a path and return the result.'''
metachars = re.compile('([*?[])')
path = metachars.sub(r'[\1]', path)
return path
def escape_whitespace(self, path):
'''Substitute whitespace with [[:space:]] and return the result.'''
whitespace = re.compile('([ \n\r\t])')
path = whitespace.sub(r'[[:space:]]', path)
return path
|