aboutsummaryrefslogtreecommitdiffstats
path: root/modules/git/command.go
blob: 22f1d02339148453048f22540b63bc7216321162 (plain)
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
// Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package git

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"code.gitea.io/gitea/modules/git/internal" //nolint:depguard // only this file can use the internal type CmdArg, other files and packages should use AddXxx functions
	"code.gitea.io/gitea/modules/gtprof"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/process"
	"code.gitea.io/gitea/modules/util"
)

// TrustedCmdArgs returns the trusted arguments for git command.
// It's mainly for passing user-provided and trusted arguments to git command
// In most cases, it shouldn't be used. Use AddXxx function instead
type TrustedCmdArgs []internal.CmdArg

var (
	// globalCommandArgs global command args for external package setting
	globalCommandArgs TrustedCmdArgs

	// defaultCommandExecutionTimeout default command execution timeout duration
	defaultCommandExecutionTimeout = 360 * time.Second
)

// DefaultLocale is the default LC_ALL to run git commands in.
const DefaultLocale = "C"

// Command represents a command with its subcommands or arguments.
type Command struct {
	prog             string
	args             []string
	globalArgsLength int
	brokenArgs       []string
	cmd              *exec.Cmd // for debug purpose only
	configArgs       []string
}

func logArgSanitize(arg string) string {
	if strings.Contains(arg, "://") && strings.Contains(arg, "@") {
		return util.SanitizeCredentialURLs(arg)
	} else if filepath.IsAbs(arg) {
		base := filepath.Base(arg)
		dir := filepath.Dir(arg)
		return ".../" + filepath.Join(filepath.Base(dir), base)
	}
	return arg
}

func (c *Command) LogString() string {
	// WARNING: this function is for debugging purposes only. It's much better than old code (which only joins args with space),
	// It's impossible to make a simple and 100% correct implementation of argument quoting for different platforms here.
	debugQuote := func(s string) string {
		if strings.ContainsAny(s, " `'\"\t\r\n") {
			return fmt.Sprintf("%q", s)
		}
		return s
	}
	a := make([]string, 0, len(c.args)+1)
	a = append(a, debugQuote(c.prog))
	if c.globalArgsLength > 0 {
		a = append(a, "...global...")
	}
	for i := c.globalArgsLength; i < len(c.args); i++ {
		a = append(a, debugQuote(logArgSanitize(c.args[i])))
	}
	return strings.Join(a, " ")
}

func (c *Command) ProcessState() string {
	if c.cmd == nil {
		return ""
	}
	return c.cmd.ProcessState.String()
}

// NewCommand creates and returns a new Git Command based on given command and arguments.
// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead.
func NewCommand(args ...internal.CmdArg) *Command {
	// Make an explicit copy of globalCommandArgs, otherwise append might overwrite it
	cargs := make([]string, 0, len(globalCommandArgs)+len(args))
	for _, arg := range globalCommandArgs {
		cargs = append(cargs, string(arg))
	}
	for _, arg := range args {
		cargs = append(cargs, string(arg))
	}
	return &Command{
		prog:             GitExecutable,
		args:             cargs,
		globalArgsLength: len(globalCommandArgs),
	}
}

// NewCommandNoGlobals creates and returns a new Git Command based on given command and arguments only with the specified args and don't use global command args
// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead.
func NewCommandNoGlobals(args ...internal.CmdArg) *Command {
	cargs := make([]string, 0, len(args))
	for _, arg := range args {
		cargs = append(cargs, string(arg))
	}
	return &Command{
		prog: GitExecutable,
		args: cargs,
	}
}

// isSafeArgumentValue checks if the argument is safe to be used as a value (not an option)
func isSafeArgumentValue(s string) bool {
	return s == "" || s[0] != '-'
}

// isValidArgumentOption checks if the argument is a valid option (starting with '-').
// It doesn't check whether the option is supported or not
func isValidArgumentOption(s string) bool {
	return s != "" && s[0] == '-'
}

// AddArguments adds new git arguments (option/value) to the command. It only accepts string literals, or trusted CmdArg.
// Type CmdArg is in the internal package, so it can not be used outside of this package directly,
// it makes sure that user-provided arguments won't cause RCE risks.
// User-provided arguments should be passed by other AddXxx functions
func (c *Command) AddArguments(args ...internal.CmdArg) *Command {
	for _, arg := range args {
		c.args = append(c.args, string(arg))
	}
	return c
}

// AddOptionValues adds a new option with a list of non-option values
// For example: AddOptionValues("--opt", val) means 2 arguments: {"--opt", val}.
// The values are treated as dynamic argument values. It equals to: AddArguments("--opt") then AddDynamicArguments(val).
func (c *Command) AddOptionValues(opt internal.CmdArg, args ...string) *Command {
	if !isValidArgumentOption(string(opt)) {
		c.brokenArgs = append(c.brokenArgs, string(opt))
		return c
	}
	c.args = append(c.args, string(opt))
	c.AddDynamicArguments(args...)
	return c
}

// AddOptionFormat adds a new option with a format string and arguments
// For example: AddOptionFormat("--opt=%s %s", val1, val2) means 1 argument: {"--opt=val1 val2"}.
func (c *Command) AddOptionFormat(opt string, args ...any) *Command {
	if !isValidArgumentOption(opt) {
		c.brokenArgs = append(c.brokenArgs, opt)
		return c
	}
	// a quick check to make sure the format string matches the number of arguments, to find low-level mistakes ASAP
	if strings.Count(strings.ReplaceAll(opt, "%%", ""), "%") != len(args) {
		c.brokenArgs = append(c.brokenArgs, opt)
		return c
	}
	s := fmt.Sprintf(opt, args...)
	c.args = append(c.args, s)
	return c
}

// AddDynamicArguments adds new dynamic argument values to the command.
// The arguments may come from user input and can not be trusted, so no leading '-' is allowed to avoid passing options.
// TODO: in the future, this function can be renamed to AddArgumentValues
func (c *Command) AddDynamicArguments(args ...string) *Command {
	for _, arg := range args {
		if !isSafeArgumentValue(arg) {
			c.brokenArgs = append(c.brokenArgs, arg)
		}
	}
	if len(c.brokenArgs) != 0 {
		return c
	}
	c.args = append(c.args, args...)
	return c
}

// AddDashesAndList adds the "--" and then add the list as arguments, it's usually for adding file list
// At the moment, this function can be only called once, maybe in future it can be refactored to support multiple calls (if necessary)
func (c *Command) AddDashesAndList(list ...string) *Command {
	c.args = append(c.args, "--")
	// Some old code also checks `arg != ""`, IMO it's not necessary.
	// If the check is needed, the list should be prepared before the call to this function
	c.args = append(c.args, list...)
	return c
}

func (c *Command) AddConfig(key, value string) *Command {
	kv := key + "=" + value
	if !isSafeArgumentValue(kv) {
		c.brokenArgs = append(c.brokenArgs, key)
	} else {
		c.configArgs = append(c.configArgs, "-c", kv)
	}
	return c
}

// ToTrustedCmdArgs converts a list of strings (trusted as argument) to TrustedCmdArgs
// In most cases, it shouldn't be used. Use NewCommand().AddXxx() function instead
func ToTrustedCmdArgs(args []string) TrustedCmdArgs {
	ret := make(TrustedCmdArgs, len(args))
	for i, arg := range args {
		ret[i] = internal.CmdArg(arg)
	}
	return ret
}

// RunOpts represents parameters to run the command. If UseContextTimeout is specified, then Timeout is ignored.
type RunOpts struct {
	Env               []string
	Timeout           time.Duration
	UseContextTimeout bool

	// Dir is the working dir for the git command, however:
	// FIXME: this could be incorrect in many cases, for example:
	// * /some/path/.git
	// * /some/path/.git/gitea-data/data/repositories/user/repo.git
	// If "user/repo.git" is invalid/broken, then running git command in it will use "/some/path/.git", and produce unexpected results
	// The correct approach is to use `--git-dir" global argument
	Dir string

	Stdout, Stderr io.Writer

	// Stdin is used for passing input to the command
	// The caller must make sure the Stdin writer is closed properly to finish the Run function.
	// Otherwise, the Run function may hang for long time or forever, especially when the Git's context deadline is not the same as the caller's.
	// Some common mistakes:
	// * `defer stdinWriter.Close()` then call `cmd.Run()`: the Run() would never return if the command is killed by timeout
	// * `go { case <- parentContext.Done(): stdinWriter.Close() }` with `cmd.Run(DefaultTimeout)`: the command would have been killed by timeout but the Run doesn't return until stdinWriter.Close()
	// * `go { if stdoutReader.Read() err != nil: stdinWriter.Close() }` with `cmd.Run()`: the stdoutReader may never return error if the command is killed by timeout
	// In the future, ideally the git module itself should have full control of the stdin, to avoid such problems and make it easier to refactor to a better architecture.
	Stdin io.Reader

	PipelineFunc func(context.Context, context.CancelFunc) error
}

func commonBaseEnvs() []string {
	envs := []string{
		// Make Gitea use internal git config only, to prevent conflicts with user's git config
		// It's better to use GIT_CONFIG_GLOBAL, but it requires git >= 2.32, so we still use HOME at the moment.
		"HOME=" + HomeDir(),
		// Avoid using system git config, it would cause problems (eg: use macOS osxkeychain to show a modal dialog, auto installing lfs hooks)
		// This might be a breaking change in 1.24, because some users said that they have put some configs like "receive.certNonceSeed" in "/etc/gitconfig"
		// For these users, they need to migrate the necessary configs to Gitea's git config file manually.
		"GIT_CONFIG_NOSYSTEM=1",
		// Ignore replace references (https://git-scm.com/docs/git-replace)
		"GIT_NO_REPLACE_OBJECTS=1",
	}

	// some environment variables should be passed to git command
	passThroughEnvKeys := []string{
		"GNUPGHOME", // git may call gnupg to do commit signing
	}
	for _, key := range passThroughEnvKeys {
		if val, ok := os.LookupEnv(key); ok {
			envs = append(envs, key+"="+val)
		}
	}
	return envs
}

// CommonGitCmdEnvs returns the common environment variables for a "git" command.
func CommonGitCmdEnvs() []string {
	return append(commonBaseEnvs(), []string{
		"LC_ALL=" + DefaultLocale,
		"GIT_TERMINAL_PROMPT=0", // avoid prompting for credentials interactively, supported since git v2.3
	}...)
}

// CommonCmdServEnvs is like CommonGitCmdEnvs, but it only returns minimal required environment variables for the "gitea serv" command
func CommonCmdServEnvs() []string {
	return commonBaseEnvs()
}

var ErrBrokenCommand = errors.New("git command is broken")

// Run runs the command with the RunOpts
func (c *Command) Run(ctx context.Context, opts *RunOpts) error {
	return c.run(ctx, 1, opts)
}

func (c *Command) run(ctx context.Context, skip int, opts *RunOpts) error {
	if len(c.brokenArgs) != 0 {
		log.Error("git command is broken: %s, broken args: %s", c.LogString(), strings.Join(c.brokenArgs, " "))
		return ErrBrokenCommand
	}
	if opts == nil {
		opts = &RunOpts{}
	}

	// We must not change the provided options
	timeout := opts.Timeout
	if timeout <= 0 {
		timeout = defaultCommandExecutionTimeout
	}

	cmdLogString := c.LogString()
	callerInfo := util.CallerFuncName(1 /* util */ + 1 /* this */ + skip /* parent */)
	if pos := strings.LastIndex(callerInfo, "/"); pos >= 0 {
		callerInfo = callerInfo[pos+1:]
	}
	// these logs are for debugging purposes only, so no guarantee of correctness or stability
	desc := fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(opts.Dir), cmdLogString)
	log.Debug("git.Command: %s", desc)

	_, span := gtprof.GetTracer().Start(ctx, gtprof.TraceSpanGitRun)
	defer span.End()
	span.SetAttributeString(gtprof.TraceAttrFuncCaller, callerInfo)
	span.SetAttributeString(gtprof.TraceAttrGitCommand, cmdLogString)

	var cancel context.CancelFunc
	var finished context.CancelFunc

	if opts.UseContextTimeout {
		ctx, cancel, finished = process.GetManager().AddContext(ctx, desc)
	} else {
		ctx, cancel, finished = process.GetManager().AddContextTimeout(ctx, timeout, desc)
	}
	defer finished()

	startTime := time.Now()

	cmd := exec.CommandContext(ctx, c.prog, append(c.configArgs, c.args...)...)
	c.cmd = cmd // for debug purpose only
	if opts.Env == nil {
		cmd.Env = os.Environ()
	} else {
		cmd.Env = opts.Env
	}

	process.SetSysProcAttribute(cmd)
	cmd.Env = append(cmd.Env, CommonGitCmdEnvs()...)
	cmd.Dir = opts.Dir
	cmd.Stdout = opts.Stdout
	cmd.Stderr = opts.Stderr
	cmd.Stdin = opts.Stdin
	if err := cmd.Start(); err != nil {
		return err
	}

	if opts.PipelineFunc != nil {
		err := opts.PipelineFunc(ctx, cancel)
		if err != nil {
			cancel()
			_ = cmd.Wait()
			return err
		}
	}

	err := cmd.Wait()
	elapsed := time.Since(startTime)
	if elapsed > time.Second {
		log.Debug("slow git.Command.Run: %s (%s)", c, elapsed)
	}

	// We need to check if the context is canceled by the program on Windows.
	// This is because Windows does not have signal checking when terminating the process.
	// It always returns exit code 1, unlike Linux, which has many exit codes for signals.
	// `err.Error()` returns "exit status 1" when using the `git check-attr` command after the context is canceled.
	if runtime.GOOS == "windows" &&
		err != nil &&
		(err.Error() == "" || err.Error() == "exit status 1") &&
		cmd.ProcessState.ExitCode() == 1 &&
		ctx.Err() == context.Canceled {
		return ctx.Err()
	}

	if err != nil && ctx.Err() != context.DeadlineExceeded {
		return err
	}

	return ctx.Err()
}

type RunStdError interface {
	error
	Unwrap() error
	Stderr() string
}

type runStdError struct {
	err    error
	stderr string
	errMsg string
}

func (r *runStdError) Error() string {
	// the stderr must be in the returned error text, some code only checks `strings.Contains(err.Error(), "git error")`
	if r.errMsg == "" {
		r.errMsg = ConcatenateError(r.err, r.stderr).Error()
	}
	return r.errMsg
}

func (r *runStdError) Unwrap() error {
	return r.err
}

func (r *runStdError) Stderr() string {
	return r.stderr
}

func IsErrorExitCode(err error, code int) bool {
	var exitError *exec.ExitError
	if errors.As(err, &exitError) {
		return exitError.ExitCode() == code
	}
	return false
}

// RunStdString runs the command with options and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr).
func (c *Command) RunStdString(ctx context.Context, opts *RunOpts) (stdout, stderr string, runErr RunStdError) {
	stdoutBytes, stderrBytes, err := c.runStdBytes(ctx, opts)
	stdout = util.UnsafeBytesToString(stdoutBytes)
	stderr = util.UnsafeBytesToString(stderrBytes)
	if err != nil {
		return stdout, stderr, &runStdError{err: err, stderr: stderr}
	}
	// even if there is no err, there could still be some stderr output, so we just return stdout/stderr as they are
	return stdout, stderr, nil
}

// RunStdBytes runs the command with options and returns stdout/stderr as bytes. and store stderr to returned error (err combined with stderr).
func (c *Command) RunStdBytes(ctx context.Context, opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) {
	return c.runStdBytes(ctx, opts)
}

func (c *Command) runStdBytes(ctx context.Context, opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) {
	if opts == nil {
		opts = &RunOpts{}
	}
	if opts.Stdout != nil || opts.Stderr != nil {
		// we must panic here, otherwise there would be bugs if developers set Stdin/Stderr by mistake, and it would be very difficult to debug
		panic("stdout and stderr field must be nil when using RunStdBytes")
	}
	stdoutBuf := &bytes.Buffer{}
	stderrBuf := &bytes.Buffer{}

	// We must not change the provided options as it could break future calls - therefore make a copy.
	newOpts := &RunOpts{
		Env:               opts.Env,
		Timeout:           opts.Timeout,
		UseContextTimeout: opts.UseContextTimeout,
		Dir:               opts.Dir,
		Stdout:            stdoutBuf,
		Stderr:            stderrBuf,
		Stdin:             opts.Stdin,
		PipelineFunc:      opts.PipelineFunc,
	}

	err := c.run(ctx, 2, newOpts)
	stderr = stderrBuf.Bytes()
	if err != nil {
		return nil, stderr, &runStdError{err: err, stderr: util.UnsafeBytesToString(stderr)}
	}
	// even if there is no err, there could still be some stderr output
	return stdoutBuf.Bytes(), stderr, nil
}

// AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests
func AllowLFSFiltersArgs() TrustedCmdArgs {
	// Now here we should explicitly allow lfs filters to run
	filteredLFSGlobalArgs := make(TrustedCmdArgs, len(globalCommandArgs))
	j := 0
	for _, arg := range globalCommandArgs {
		if strings.Contains(string(arg), "lfs") {
			j--
		} else {
			filteredLFSGlobalArgs[j] = arg
			j++
		}
	}
	return filteredLFSGlobalArgs[:j]
}
630/stable31 Nextcloud server, a safe home for all your data: https://github.com/nextcloud/serverwww-data
aboutsummaryrefslogtreecommitdiffstats
path: root/l10n/ug/lib.po
blob: ad48833815be96317c2918ab93e084e44111f39c (plain)
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# 
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: ownCloud\n"
"Report-Msgid-Bugs-To: translations@owncloud.org\n"
"POT-Creation-Date: 2014-06-05 01:54-0400\n"
"PO-Revision-Date: 2014-06-05 05:54+0000\n"
"Last-Translator: I Robot\n"
"Language-Team: Uighur (http://www.transifex.com/projects/p/owncloud/language/ug/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ug\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#: base.php:714
msgid "You are accessing the server from an untrusted domain."
msgstr ""

#: base.php:715
msgid ""
"Please contact your administrator. If you are an administrator of this "
"instance, configure the \"trusted_domain\" setting in config/config.php. An "
"example configuration is provided in config/config.sample.php."
msgstr ""

#: private/app.php:245
#, php-format
msgid ""
"App \"%s\" can't be installed because it is not compatible with this version"
" of ownCloud."
msgstr ""

#: private/app.php:261
msgid "No app name specified"
msgstr ""

#: private/app.php:367
msgid "Help"
msgstr "ياردەم"

#: private/app.php:380
msgid "Personal"
msgstr "شەخسىي"

#: private/app.php:391
msgid "Settings"
msgstr "تەڭشەكلەر"

#: private/app.php:403
msgid "Users"
msgstr "ئىشلەتكۈچىلەر"

#: private/app.php:416
msgid "Admin"
msgstr ""

#: private/app.php:903
#, php-format
msgid "Failed to upgrade \"%s\"."
msgstr ""

#: private/avatar.php:66
msgid "Unknown filetype"
msgstr ""

#: private/avatar.php:71
msgid "Invalid image"
msgstr ""

#: private/defaults.php:35
msgid "web services under your control"
msgstr ""

#: private/installer.php:64
msgid "No source specified when installing app"
msgstr ""

#: private/installer.php:71
msgid "No href specified when installing app from http"
msgstr ""

#: private/installer.php:76
msgid "No path specified when installing app from local file"
msgstr ""

#: private/installer.php:90
#, php-format
msgid "Archives of type %s are not supported"
msgstr ""

#: private/installer.php:104
msgid "Failed to open archive when installing app"
msgstr ""

#: private/installer.php:126
msgid "App does not provide an info.xml file"
msgstr ""

#: private/installer.php:132
msgid "App can't be installed because of not allowed code in the App"
msgstr ""

#: private/installer.php:138
msgid ""
"App can't be installed because it is not compatible with this version of "
"ownCloud"
msgstr ""

#: private/installer.php:144
msgid ""
"App can't be installed because it contains the <shipped>true</shipped> tag "
"which is not allowed for non shipped apps"
msgstr ""

#: private/installer.php:157
msgid ""
"App can't be installed because the version in info.xml/version is not the "
"same as the version reported from the app store"
msgstr ""

#: private/installer.php:167
msgid "App directory already exists"
msgstr ""

#: private/installer.php:180
#, php-format
msgid "Can't create app folder. Please fix permissions. %s"
msgstr ""

#: private/json.php:29
msgid "Application is not enabled"
msgstr ""

#: private/json.php:40 private/json.php:62 private/json.php:87
msgid "Authentication error"
msgstr "سالاھىيەت دەلىللەش خاتالىقى"

#: private/json.php:51
msgid "Token expired. Please reload page."
msgstr ""

#: private/json.php:74
msgid "Unknown user"
msgstr ""

#: private/search/provider/file.php:18 private/search/provider/file.php:36
msgid "Files"
msgstr "ھۆججەتلەر"

#: private/search/provider/file.php:27 private/search/provider/file.php:34
msgid "Text"
msgstr "قىسقا ئۇچۇر"

#: private/search/provider/file.php:30
msgid "Images"
msgstr "سۈرەتلەر"

#: private/setup/abstractdatabase.php:26
#, php-format
msgid "%s enter the database username."
msgstr ""

#: private/setup/abstractdatabase.php:29
#, php-format
msgid "%s enter the database name."
msgstr ""

#: private/setup/abstractdatabase.php:32
#, php-format
msgid "%s you may not use dots in the database name"
msgstr ""

#: private/setup/mssql.php:20
#, php-format
msgid "MS SQL username and/or password not valid: %s"
msgstr ""

#: private/setup/mssql.php:21 private/setup/mysql.php:13
#: private/setup/oci.php:114 private/setup/postgresql.php:31
#: private/setup/postgresql.php:84
msgid "You need to enter either an existing account or the administrator."
msgstr ""

#: private/setup/mysql.php:12
msgid "MySQL/MariaDB username and/or password not valid"
msgstr ""

#: private/setup/mysql.php:67 private/setup/oci.php:54
#: private/setup/oci.php:121 private/setup/oci.php:144
#: private/setup/oci.php:151 private/setup/oci.php:162
#: private/setup/oci.php:169 private/setup/oci.php:178
#: private/setup/oci.php:186 private/setup/oci.php:195
#: private/setup/oci.php:201 private/setup/postgresql.php:103
#: private/setup/postgresql.php:112 private/setup/postgresql.php:129
#: private/setup/postgresql.php:139 private/setup/postgresql.php:148
#, php-format
msgid "DB Error: \"%s\""
msgstr ""

#: private/setup/mysql.php:68 private/setup/oci.php:55
#: private/setup/oci.php:122 private/setup/oci.php:145
#: private/setup/oci.php:152 private/setup/oci.php:163
#: private/setup/oci.php:179 private/setup/oci.php:187
#: private/setup/oci.php:196 private/setup/postgresql.php:104
#: private/setup/postgresql.php:113 private/setup/postgresql.php:130
#: private/setup/postgresql.php:140 private/setup/postgresql.php:149
#, php-format
msgid "Offending command was: \"%s\""
msgstr ""

#: private/setup/mysql.php:85
#, php-format
msgid "MySQL/MariaDB user '%s'@'localhost' exists already."
msgstr ""

#: private/setup/mysql.php:86
msgid "Drop this user from MySQL/MariaDB"
msgstr ""

#: private/setup/mysql.php:91
#, php-format
msgid "MySQL/MariaDB user '%s'@'%%' already exists"
msgstr ""

#: private/setup/mysql.php:92
msgid "Drop this user from MySQL/MariaDB."
msgstr ""

#: private/setup/oci.php:34
msgid "Oracle connection could not be established"
msgstr ""

#: private/setup/oci.php:41 private/setup/oci.php:113
msgid "Oracle username and/or password not valid"
msgstr ""

#: private/setup/oci.php:170 private/setup/oci.php:202
#, php-format
msgid "Offending command was: \"%s\", name: %s, password: %s"
msgstr ""

#: private/setup/postgresql.php:30 private/setup/postgresql.php:83
msgid "PostgreSQL username and/or password not valid"
msgstr ""

#: private/setup.php:28
msgid "Set an admin username."
msgstr ""

#: private/setup.php:31
msgid "Set an admin password."
msgstr ""

#: private/setup.php:164
msgid ""
"Your web server is not yet properly setup to allow files synchronization "
"because the WebDAV interface seems to be broken."
msgstr "سىزنىڭ تور مۇلازىمېتىرىڭىز ھۆججەت قەدەمداشلاشقا يول قويىدىغان قىلىپ توغرا تەڭشەلمەپتۇ، چۈنكى WebDAV نىڭ ئېغىزى بۇزۇلغاندەك تۇرىدۇ."

#: private/setup.php:165
#, php-format
msgid "Please double check the <a href='%s'>installation guides</a>."
msgstr ""

#: private/share/mailnotifications.php:91
#: private/share/mailnotifications.php:137
#, php-format
msgid "%s shared »%s« with you"
msgstr ""

#: private/share/share.php:494
#, php-format
msgid "Sharing %s failed, because the file does not exist"
msgstr ""

#: private/share/share.php:501
#, php-format
msgid "You are not allowed to share %s"
msgstr ""

#: private/share/share.php:526
#, php-format
msgid "Sharing %s failed, because the user %s is the item owner"
msgstr ""

#: private/share/share.php:532
#, php-format
msgid "Sharing %s failed, because the user %s does not exist"
msgstr ""

#: private/share/share.php:541
#, php-format
msgid ""
"Sharing %s failed, because the user %s is not a member of any groups that %s"
" is a member of"
msgstr ""

#: private/share/share.php:554 private/share/share.php:582
#, php-format
msgid "Sharing %s failed, because this item is already shared with %s"
msgstr ""

#: private/share/share.php:562
#, php-format
msgid "Sharing %s failed, because the group %s does not exist"
msgstr ""

#: private/share/share.php:569
#, php-format
msgid "Sharing %s failed, because %s is not a member of the group %s"
msgstr ""

#: private/share/share.php:621
msgid ""
"You need to provide a password to create a public link, only protected links"
" are allowed"
msgstr ""

#: private/share/share.php:641
#, php-format
msgid "Sharing %s failed, because sharing with links is not allowed"
msgstr ""

#: private/share/share.php:648
#, php-format
msgid "Share type %s is not valid for %s"
msgstr ""

#: private/share/share.php:787
#, php-format
msgid ""
"Setting permissions for %s failed, because the permissions exceed "
"permissions granted to %s"
msgstr ""

#: private/share/share.php:848
#, php-format
msgid "Setting permissions for %s failed, because the item was not found"
msgstr ""

#: private/share/share.php:959
#, php-format
msgid "Sharing backend %s must implement the interface OCP\\Share_Backend"
msgstr ""

#: private/share/share.php:966
#, php-format
msgid "Sharing backend %s not found"
msgstr ""

#: private/share/share.php:972
#, php-format
msgid "Sharing backend for %s not found"
msgstr ""

#: private/share/share.php:1388
#, php-format
msgid "Sharing %s failed, because the user %s is the original sharer"
msgstr ""

#: private/share/share.php:1397
#, php-format
msgid ""
"Sharing %s failed, because the permissions exceed permissions granted to %s"
msgstr ""

#: private/share/share.php:1413
#, php-format
msgid "Sharing %s failed, because resharing is not allowed"
msgstr ""

#: private/share/share.php:1425
#, php-format
msgid ""
"Sharing %s failed, because the sharing backend for %s could not find its "
"source"
msgstr ""

#: private/share/share.php:1439
#, php-format
msgid ""
"Sharing %s failed, because the file could not be found in the file cache"
msgstr ""

#: private/tags.php:183
#, php-format
msgid "Could not find category \"%s\""
msgstr ""

#: private/template/functions.php:134
msgid "seconds ago"
msgstr ""

#: private/template/functions.php:135
msgid "%n minute ago"
msgid_plural "%n minutes ago"
msgstr[0] ""

#: private/template/functions.php:136
msgid "%n hour ago"
msgid_plural "%n hours ago"
msgstr[0] ""

#: private/template/functions.php:137
msgid "today"
msgstr "بۈگۈن"

#: private/template/functions.php:138
msgid "yesterday"
msgstr "تۈنۈگۈن"

#: private/template/functions.php:140
msgid "%n day go"
msgid_plural "%n days ago"
msgstr[0] ""

#: private/template/functions.php:142
msgid "last month"
msgstr ""

#: private/template/functions.php:143
msgid "%n month ago"
msgid_plural "%n months ago"
msgstr[0] ""

#: private/template/functions.php:145
msgid "last year"
msgstr ""

#: private/template/functions.php:146
msgid "years ago"
msgstr ""

#: private/user/manager.php:238
msgid ""
"Only the following characters are allowed in a username: \"a-z\", \"A-Z\", "
"\"0-9\", and \"_.@-\""
msgstr ""

#: private/user/manager.php:243
msgid "A valid username must be provided"
msgstr "چوقۇم ئىناۋەتلىك ئىشلەتكۈچى ئىسمىدىن بىرنى تەمىنلەش كېرەك"

#: private/user/manager.php:247
msgid "A valid password must be provided"
msgstr "چوقۇم ئىناۋەتلىك ئىم تەمىنلەش كېرەك"

#: private/user/manager.php:252
msgid "The username is already being used"
msgstr ""

#: public/files/locknotacquiredexception.php:39
#, php-format
msgid "Could not obtain lock type %d on \"%s\"."
msgstr ""