def write(path, contents, encoding=None): """Writes a raw string to a file.""" with open(core.encode(path), 'wb') as fh: try: core.write(fh, core.encode(contents, encoding=encoding)) except IOError: fh.close()
def fork(args): """Launch a command in the background.""" if os.name in ('nt', 'dos'): # Windows is absolutely insane. # # If we want to launch 'gitk' we have to use the 'sh -c' trick. # # If we want to launch 'git.exe' we have to expand all filenames # after the double-dash. # # os.spawnv wants an absolute path in the command name but not in # the command vector. Wow. enc_args = win32_expand_paths([core.encode(a) for a in args]) abspath = win32_abspath(enc_args[0]) if abspath: # e.g. fork(['git', 'difftool', '--no-prompt', '--', 'path']) return os.spawnv(os.P_NOWAIT, abspath, enc_args) # e.g. fork(['gitk', '--all']) sh_exe = win32_abspath('sh') enc_argv = map(shell_quote, enc_args) cmdstr = ' '.join(enc_argv) cmd = ['sh.exe', '-c', cmdstr] return os.spawnv(os.P_NOWAIT, sh_exe, cmd) else: # Unix is absolutely simple enc_args = [core.encode(a) for a in args] enc_argv = map(shell_quote, enc_args) cmdstr = ' '.join(enc_argv) return os.system(cmdstr + '&')
def test_core_encode(self): """Test the core.encode function """ filename = helper.fixture('unicode.txt') expect = core.encode('unicøde') actual = core.encode(core.read(filename).strip()) self.assertEqual(expect, actual)
def mimedata_from_paths(paths): """Return mimedata with a list of absolute path URLs""" abspaths = [core.abspath(path) for path in paths] urls = [QtCore.QUrl.fromLocalFile(path) for path in abspaths] unicode_urls = [core.decode(bytes(url.toEncoded().data())) for url in urls] raw_text = core.encode('\r\n'.join(unicode_urls) + '\r\n') text = QtCore.QByteArray(raw_text) mimedata = QtCore.QMimeData() mimedata.setUrls(urls) mimedata.setData('text/plain', text) mimedata.setData('UTF8_STRING', text) mimedata.setData('COMPOUND_TEXT', text) mimedata.setData('TEXT', text) mimedata.setData('STRING', text) mimedata.setData('text/plain;charset=utf-8', text) # The text/x-moz-list format is raw text encoded in utf-16. # Failure to encode prevents gnome-terminal from getting the right paths. moz_text = subprocess.list2cmdline(abspaths) moz_text = core.encode(moz_text, encoding='utf-16') mimedata.setData('text/x-moz-url', moz_text) return mimedata
def setenv(key, value): """Compatibility wrapper for setting environment variables Why? win32 requires putenv(). UNIX only requires os.environ. """ os.environ[key] = core.encode(value) os.putenv(key, core.encode(value))
def url_for_email(email, imgsize): email_hash = core.decode(hashlib.md5(core.encode(email)).hexdigest()) # Python2.6 requires byte strings for urllib.quote() so we have # to force default_url = 'https://git-cola.github.io/images/git-64x64.jpg' encoded_url = urllib.quote(core.encode(default_url), core.encode('')) query = '?s=%d&d=%s' % (imgsize, core.decode(encoded_url)) url = 'https://gravatar.com/avatar/' + email_hash + query return url
def color(self, key, default): string = self.get('cola.color.%s' % key, default) string = core.encode(string) default = core.encode(default) struct_layout = core.encode('BBB') try: r, g, b = struct.unpack(struct_layout, unhexlify(string)) except Exception: r, g, b = struct.unpack(struct_layout, unhexlify(default)) return (r, g, b)
def icon_file(filename, staged=False, untracked=False): """Returns a file path representing a corresponding file path.""" if staged: if os.path.exists(core.encode(filename)): ifile = resources.icon('staged-item.png') else: ifile = resources.icon('removed.png') elif untracked: ifile = resources.icon('untracked.png') else: ifile = utils.file_icon(core.encode(filename)) return ifile
def _refresh_watches(self, paths_to_watch, wd_set, wd_map): watched_paths = set(wd_map) for path in watched_paths - paths_to_watch: wd = wd_map.pop(path) wd_set.remove(wd) try: inotify.rm_watch(self._inotify_fd, wd) except OSError as e: if e.errno == errno.EINVAL: # This error can occur if the target of the wd was # removed on the filesystem before we call # inotify.rm_watch() so ignore it. pass else: raise for path in paths_to_watch - watched_paths: try: wd = inotify.add_watch(self._inotify_fd, core.encode(path), self._ADD_MASK) except OSError as e: if e.errno in (errno.ENOENT, errno.ENOTDIR): # These two errors should only occur as a result of # race conditions: the first if the directory # referenced by path was removed or renamed before the # call to inotify.add_watch(); the second if the # directory referenced by path was replaced with a file # before the call to inotify.add_watch(). Therefore we # simply ignore them. pass else: raise else: wd_set.add(wd) wd_map[path] = wd
def paths(self): all_refs = utils.shell_split(self.ref) if '--' in all_refs: all_refs = all_refs[all_refs.index('--'):] return [p for p in all_refs if p and os.path.exists(core.encode(p))]
def url_for_email(email, imgsize): email_hash = hashlib.md5(core.encode(email)).hexdigest() default_url = b"https://git-cola.github.io/images/git-64x64.jpg" encoded_url = urllib.quote(default_url, b"") query = "?s=%d&d=%s" % (imgsize, encoded_url) url = "https://gravatar.com/avatar/" + email_hash + query return url
def url_for_email(email, imgsize): email_hash = hashlib.md5(core.encode(email)).hexdigest() default_url = b'http://git-cola.github.io/images/git-64x64.jpg' encoded_url = urllib.quote(default_url, b'') query = '?s=%d&d=%s' % (imgsize, encoded_url) url = 'http://gravatar.com/avatar/' + email_hash + query return url
def _new(self): dlg = QtGui.QFileDialog(self) dlg.setFileMode(QtGui.QFileDialog.Directory) dlg.setOption(QtGui.QFileDialog.ShowDirsOnly) if dlg.exec_() != QtGui.QFileDialog.Accepted: return paths = dlg.selectedFiles() if not paths: return upath = unicode(paths[0]) if not upath: return path = core.encode(unicode(paths[0])) # Avoid needlessly calling `git init`. if git.is_git_dir(path): # We could prompt here and confirm that they really didn't # mean to open an existing repository, but I think # treating it like an "Open" is a sensible DWIM answer. self._gitdir = upath self.accept() return os.chdir(path) status, out, err = utils.run_command(['git', 'init']) if status != 0: title = 'Error Creating Repository' msg = 'git init returned exit status %d' % status details = 'output:\n%s\n\nerrors:\n%s' % (out, err) qtutils.critical(title, msg, details) else: self._gitdir = upath self.accept()
def stage_paths(self, paths): """Stages add/removals to git.""" if not paths: self.stage_all() return add = [] remove = [] for path in set(paths): if os.path.exists(core.encode(path)): add.append(path) else: remove.append(path) self.notify_observers(self.message_about_to_update) # `git add -u` doesn't work on untracked files if add: self._sliced_add(add) # If a path doesn't exist then that means it should be removed # from the index. We use `git add -u` for that. if remove: while remove: self.git.add('--', u=True, with_stderr=True, *remove[:42]) remove = remove[42:] self._update_files() self.notify_observers(self.message_updated)
def test_core_encode(self): """Test the core.encode function """ filename = helper.fixture('unicode.txt') fh = open(filename) content = core.read_nointr(fh).strip() fh.close() self.assertEqual(content, core.encode(u'unicøde'))
def clone_repo(spawn=True): """ Present GUI controls for cloning a repository A new cola session is invoked when 'spawn' is True. """ url, ok = qtutils.prompt('Path or URL to clone (Env. $VARS okay)') url = os.path.expandvars(core.encode(url)) if not ok or not url: return None try: # Pick a suitable basename by parsing the URL newurl = url.replace('\\', '/') default = newurl.rsplit('/', 1)[-1] if default == '.git': # The end of the URL is /.git, so assume it's a file path default = os.path.basename(os.path.dirname(newurl)) if default.endswith('.git'): # The URL points to a bare repo default = default[:-4] if url == '.': # The URL is the current repo default = os.path.basename(os.getcwd()) if not default: raise except: cola.notifier().broadcast(signals.information, 'Error Cloning', 'Could not parse: "%s"' % url) qtutils.log(1, 'Oops, could not parse git url: "%s"' % url) return None # Prompt the user for a directory to use as the parent directory parent = QtGui.QApplication.instance().activeWindow() msg = 'Select a parent directory for the new clone' dirname = qtutils.opendir_dialog(parent, msg, cola.model().getcwd()) if not dirname: return None count = 1 dirname = core.decode(dirname) destdir = os.path.join(dirname, core.decode(default)) olddestdir = destdir if os.path.exists(destdir): # An existing path can be specified msg = ('"%s" already exists, cola will create a new directory' % destdir) cola.notifier().broadcast(signals.information, 'Directory Exists', msg) # Make sure the new destdir doesn't exist while os.path.exists(destdir): destdir = olddestdir + str(count) count += 1 cola.notifier().broadcast(signals.clone, core.decode(url), destdir, spawn=spawn) return destdir
def clone_repo(spawn=True): """ Present GUI controls for cloning a repository A new cola session is invoked when 'spawn' is True. """ url, ok = qtutils.prompt(N_('Path or URL to clone (Env. $VARS okay)')) url = os.path.expandvars(core.encode(url)) if not ok or not url: return None try: # Pick a suitable basename by parsing the URL newurl = url.replace('\\', '/').rstrip('/') default = newurl.rsplit('/', 1)[-1] if default == '.git': # The end of the URL is /.git, so assume it's a file path default = os.path.basename(os.path.dirname(newurl)) if default.endswith('.git'): # The URL points to a bare repo default = default[:-4] if url == '.': # The URL is the current repo default = os.path.basename(os.getcwd()) if not default: raise except: Interaction.information( N_('Error Cloning'), N_('Could not parse Git URL: "%s"') % url) Interaction.log(N_('Could not parse Git URL: "%s"') % url) return None # Prompt the user for a directory to use as the parent directory msg = N_('Select a parent directory for the new clone') dirname = qtutils.opendir_dialog(msg, cola.model().getcwd()) if not dirname: return None count = 1 dirname = core.decode(dirname) destdir = os.path.join(dirname, core.decode(default)) olddestdir = destdir if os.path.exists(destdir): # An existing path can be specified msg = (N_('"%s" already exists, cola will create a new directory') % destdir) Interaction.information('Directory Exists', msg) # Make sure the new destdir doesn't exist while os.path.exists(destdir): destdir = olddestdir + str(count) count += 1 if cmds.do(cmds.Clone, core.decode(url), destdir, spawn=spawn): return destdir return None
def do(self): context = self.context ref = core.encode(context.ref) relpath = core.encode(context.relpath) cmd = ['git', 'show', '%s:%s' % (ref, relpath)] fp = open(core.encode(context.filename), 'wb') proc = utils.start_command(cmd, stdout=fp) out, err = proc.communicate() fp.close() status = proc.returncode msg = ('Saved "%s" from %s to "%s"' % (context.relpath, context.ref, context.filename)) cola.notifier().broadcast(signals.log_cmd, status, msg) self.factory.prompt_user(signals.information, 'File Saved', 'File saved to "%s"' % context.filename)
def clone_repo(spawn=True): """ Present GUI controls for cloning a repository A new cola session is invoked when 'spawn' is True. """ url, ok = qtutils.prompt('Path or URL to clone (Env. $VARS okay)') url = os.path.expandvars(core.encode(url)) if not ok or not url: return None try: # Pick a suitable basename by parsing the URL newurl = url.replace('\\', '/') default = newurl.rsplit('/', 1)[-1] if default == '.git': # The end of the URL is /.git, so assume it's a file path default = os.path.basename(os.path.dirname(newurl)) if default.endswith('.git'): # The URL points to a bare repo default = default[:-4] if url == '.': # The URL is the current repo default = os.path.basename(os.getcwd()) if not default: raise except: Interaction.information( 'Error Cloning', 'Could not parse: "%s"' % url) Interaction.log('Oops, could not parse git url: "%s"' % url) return None # Prompt the user for a directory to use as the parent directory msg = 'Select a parent directory for the new clone' dirname = qtutils.opendir_dialog(msg, cola.model().getcwd()) if not dirname: return None count = 1 dirname = core.decode(dirname) destdir = os.path.join(dirname, core.decode(default)) olddestdir = destdir if os.path.exists(destdir): # An existing path can be specified msg = ('"%s" already exists, cola will create a new directory' % destdir) Interaction.information('Directory Exists', msg) # Make sure the new destdir doesn't exist while os.path.exists(destdir): destdir = olddestdir + str(count) count += 1 cmds.do(cmds.Clone, core.decode(url), destdir, spawn=spawn) return destdir
def do(self): model = self.model ref = core.encode(model.ref) relpath = core.encode(model.relpath) cmd = ['git', 'show', '%s:%s' % (ref, relpath)] fp = open(core.encode(model.filename), 'wb') proc = utils.start_command(cmd, stdout=fp) out, err = proc.communicate() fp.close() status = proc.returncode msg = ('Saved "%s" from %s to "%s"' % (model.relpath, model.ref, model.filename)) Interaction.log_status(status, msg, '') Interaction.information( 'File Saved', 'File saved to "%s"' % model.filename)
def __init__(self): Command.__init__(self) untracked = self.model.untracked suffix = len(untracked) > 1 and 's' or '' io = StringIO() io.write('# %s untracked file%s\n' % (len(untracked), suffix)) if untracked: io.write('# possible .gitignore rule%s:\n' % suffix) for u in untracked: io.write('/'+core.encode(u)) self.new_diff_text = core.decode(io.getvalue())
def __init__(self): Command.__init__(self) untracked = self.model.untracked suffix = len(untracked) > 1 and "s" or "" io = StringIO() io.write("# %s untracked file%s\n" % (len(untracked), suffix)) if untracked: io.write("# possible .gitignore rule%s:\n" % suffix) for u in untracked: io.write("/" + core.encode(u)) self.new_diff_text = core.decode(io.getvalue()) self.new_mode = self.model.mode_untracked
def __init__(self): Command.__init__(self) untracked = self.model.untracked suffix = len(untracked) > 1 and 's' or '' io = StringIO() io.write('# %s untracked file%s\n' % (len(untracked), suffix)) if untracked: io.write('# possible .gitignore rule%s:\n' % suffix) for u in untracked: io.write('/'+core.encode(u)) self.new_diff_text = core.decode(io.getvalue()) self.new_mode = self.model.mode_untracked
def do(self): fp = open(core.encode(self.filename), 'wb') cmd = ['git', 'archive', '--format='+self.fmt] if self.fmt in ('tgz', 'tar.gz'): cmd.append('-9') if self.prefix: cmd.append('--prefix=' + self.prefix) cmd.append(self.ref) proc = utils.start_command(cmd, stdout=fp) out, err = proc.communicate() fp.close() status = proc.returncode Interaction.log_status(status, out or '', err or '')
def do(self): fp = open(core.encode(self.filename), 'wb') cmd = ['git', 'archive', '--format=' + self.fmt] if self.fmt in ('tgz', 'tar.gz'): cmd.append('-9') if self.prefix: cmd.append('--prefix=' + self.prefix) cmd.append(self.ref) proc = utils.start_command(cmd, stdout=fp) out, err = proc.communicate() fp.close() status = proc.returncode Interaction.log_status(status, out or '', err or '')
def do(self): fp = open(core.encode(self.filename), "wb") cmd = ["git", "archive", "--format=" + self.fmt] if self.fmt in ("tgz", "tar.gz"): cmd.append("-9") if self.prefix: cmd.append("--prefix=" + self.prefix) cmd.append(self.ref) proc = utils.start_command(cmd, stdout=fp) out, err = proc.communicate() fp.close() status = proc.returncode Interaction.log_status(status, out or "", err or "")
def remove_missing(self): missing_bookmarks = [] missing_recent = [] for bookmark in self.bookmarks: if not self.verify(core.encode(bookmark)): missing_bookmarks.append(bookmark) for bookmark in missing_bookmarks: try: self.bookmarks.remove(bookmark) except: pass for recent in self.recent: if not self.verify(core.encode(recent)): missing_recent.append(recent) for recent in missing_recent: try: self.recent.remove(recent) except: pass
def _watch_directory(self, directory): """Set up a directory for monitoring by inotify""" if self._wmgr is None or self._add_watch_failed: return directory = core.realpath(directory) if directory in self._dirs_seen: return self._dirs_seen.add(directory) if core.exists(directory): dir_arg = directory if PY3 else core.encode(directory) try: self._wmgr.add_watch(dir_arg, self._mask, quiet=False) except WatchManagerError as e: self._add_watch_failed = True self._add_watch_failed_warning(directory, e)
def _watch_directory(self, directory): """Set up a directory for monitoring by inotify""" if self._wmgr is None or self._add_watch_failed: return directory = core.realpath(directory) if directory in self._dirs_seen: return self._dirs_seen.add(directory) if core.exists(directory): encoded_dir = core.encode(directory) try: self._wmgr.add_watch(encoded_dir, self._mask, quiet=False) except WatchManagerError as e: self._add_watch_failed = True self._add_watch_failed_warning(directory, e)
def do(self): fp = open(core.encode(self.filename), 'wb') cmd = ['git', 'archive', '--format='+self.fmt] if self.fmt in ('tgz', 'tar.gz'): cmd.append('-9') if self.prefix: cmd.append('--prefix=' + self.prefix) cmd.append(self.ref) proc = utils.start_command(cmd, stdout=fp) out, err = proc.communicate() fp.close() if not out: out = '' if err: out += err status = proc.returncode cola.notifier().broadcast(signals.log_cmd, status, out)
def stage_paths(self, paths): """Stages add/removals to git.""" add = [] remove = [] for path in set(paths): if os.path.exists(core.encode(path)): add.append(path) else: remove.append(path) # `git add -u` doesn't work on untracked files if add: self.git.add('--', *add) # If a path doesn't exist then that means it should be removed # from the index. We use `git add -u` for that. if remove: self.git.add('--', u=True, *remove) self.update_status()
def date(self): """ Returns a relative date for a file path. This is typically used for new entries that do not have 'git log' information. """ encpath = core.encode(self.path) st = os.stat(encpath) elapsed = time.time() - st.st_mtime minutes = int(elapsed / 60.) if minutes < 60: return '%d minutes ago' % minutes hours = int(elapsed / 60. / 60.) if hours < 24: return '%d hours ago' % hours return '%d days ago' % int(elapsed / 60. / 60. / 24.)
def date(self): """ Returns a relative date for a file path. This is typically used for new entries that do not have 'git log' information. """ encpath = core.encode(self.path) st = os.stat(encpath) elapsed = time.time() - st.st_mtime minutes = int(elapsed / 60.) if minutes < 60: return N_('%d minutes ago') % minutes hours = int(elapsed / 60. / 60.) if hours < 24: return N_('%d hours ago') % hours return N_('%d days ago') % int(elapsed / 60. / 60. / 24.)
def fork(args): """Launch a command in the background.""" if is_win32(): # Windows is absolutely insane. enc_args = map(core.encode, args) abspath = win32_abspath(args[0]) if abspath: # e.g. fork(['git', 'difftool', '--no-prompt', '--', 'path']) return os.spawnv(os.P_NOWAIT, abspath, enc_args) else: # e.g. fork(['gitk', '--all']) cmdstr = subprocess.list2cmdline(enc_args) sh_exe = win32_abspath('sh') cmd = ['sh.exe', '-c', cmdstr] return os.spawnv(os.P_NOWAIT, sh_exe, cmd) else: # I like having a sane os.system() enc_args = [core.encode(a) for a in args] cmdstr = subprocess.list2cmdline(enc_args) return os.system(cmdstr + '&')
def mimedata_from_paths(paths): """Return mimedata with a list of absolute path URLs""" abspaths = [core.abspath(path) for path in paths] urls = [QtCore.QUrl.fromLocalFile(path) for path in abspaths] mimedata = QtCore.QMimeData() mimedata.setUrls(urls) # The text/x-moz-list format is always included by Qt, and doing # mimedata.removeFormat('text/x-moz-url') has no effect. # C.f. http://www.qtcentre.org/threads/44643-Dragging-text-uri-list-Qt-inserts-garbage # # gnome-terminal expects utf-16 encoded text, but other terminals, # e.g. terminator, prefer utf-8, so allow cola.dragencoding # to override the default. paths_text = subprocess.list2cmdline(abspaths) encoding = gitcfg.current().get('cola.dragencoding', 'utf-16') moz_text = core.encode(paths_text, encoding=encoding) mimedata.setData('text/x-moz-url', moz_text) return mimedata
def mimedata_from_paths(paths): """Return mimedata with a list of absolute path URLs""" abspaths = [core.abspath(path) for path in paths] urls = [QtCore.QUrl.fromLocalFile(path) for path in abspaths] mimedata = QtCore.QMimeData() mimedata.setUrls(urls) # The text/x-moz-list format is always included by Qt, and doing # mimedata.removeFormat('text/x-moz-url') has no effect. # C.f. http://www.qtcentre.org/threads/44643-Dragging-text-uri-list-Qt-inserts-garbage # # gnome-terminal expects utf-16 encoded text, but other terminals, # e.g. terminator, prefer utf-8, so allow cola.dragencoding # to override the default. paths_text = core.list2cmdline(abspaths) encoding = gitcfg.current().get('cola.dragencoding', 'utf-16') moz_text = core.encode(paths_text, encoding=encoding) mimedata.setData('text/x-moz-url', moz_text) return mimedata
def _new(self): dlg = QtGui.QFileDialog(self) dlg.setFileMode(QtGui.QFileDialog.Directory) dlg.setOption(QtGui.QFileDialog.ShowDirsOnly) if dlg.exec_() != QtGui.QFileDialog.Accepted: return paths = dlg.selectedFiles() if not paths: return upath = unicode(paths[0]) if not upath: return path = core.encode(unicode(paths[0])) # Avoid needlessly calling `git init`. if git.is_git_dir(path): # We could prompt here and confirm that they really didn't # mean to open an existing repository, but I think # treating it like an "Open" is a sensible DWIM answer. self._gitdir = upath self.accept() return os.chdir(path) status, out, err = utils.run_command(['git', 'init']) if status != 0: title = N_('Error Creating Repository') msg = (N_('"%(command)s" returned exit status %(status)d') % dict(command='git init', status=status)) details = N_('Output:\n%s') % out if err: details += '\n\n' details += N_('Errors: %s') % err qtutils.critical(title, msg, details) else: self._gitdir = upath self.accept()
def _shell_split(s): """Split string apart into utf-8 encoded words using shell syntax""" try: return shlex.split(core.encode(s)) except ValueError: return [core.encode(s)]
def test_core_encode(self): """Test the core.encode function""" filename = helper.fixture('unicode.txt') expect = core.encode('unicøde') actual = core.encode(core.read(filename).strip()) self.assertEqual(expect, actual)
def __init__(self, amend, msg): ResetMode.__init__(self) self.amend = amend self.msg = core.encode(msg) self.old_commitmsg = self.model.commitmsg self.new_commitmsg = ''
def goto_grep(line): """Called when Search -> Grep's right-click 'goto' action.""" filename, line_number, contents = line.split(':', 2) filename = core.encode(filename) cola.notifier().broadcast(signals.edit, [filename], line_number=line_number)
def _fork_posix(args): """Launch a process in the background.""" encoded_args = [core.encode(arg) for arg in args] return subprocess.Popen(encoded_args).pid
def test_core_decode(): """Test the core.decode function""" filename = helper.fixture('unicode.txt') expect = core.decode(core.encode('unicøde')) actual = core.read(filename).strip() assert expect == actual
def goto_grep(line): """Called when Search -> Grep's right-click 'goto' action.""" filename, line_number, contents = line.split(':', 2) filename = core.encode(filename) do(cmds.Edit, [filename], line_number=line_number)
def slurp(path): """Slurps a filepath into a string.""" fh = open(core.encode(path)) slushy = core.read(fh) fh.close() return core.decode(slushy)
def write(path, contents, encoding=None): """Writes a raw string to a file.""" fh = open(core.encode(path), 'wb') core.write(fh, core.encode(contents, encoding=encoding)) fh.close()
def log(cls, message): if cls.VERBOSE: print(core.encode(message))
def __init__(self, name, revision, sign=False, message=''): Command.__init__(self) self._name = name self._message = core.encode(message) self._revision = revision self._sign = sign