简体   繁体   English

Git-gitignore / exclude中的最大深度

[英]Git - max depth in gitignore/exclude

A short story: I have recently made a clean install of Arch Linux on my PC because my old install got very bloated with unnecessary packages and config directories. 一个简短的故事:我最近在我的PC上进行了Arch Linux的全新安装,因为我的旧安装由于不必要的软件包和配置目录而变得肿。 Now I want to keep my home directory clean and simple. 现在,我要保持主目录的简洁明了。 I decided to use git to supervise every file and folder there but I can't just exclude every log(or any other constantly updating dir/file) as it is too much of a hassle. 我决定使用git来监督那里的每个文件和文件夹,但我不能仅仅排除每个日志(或任何其他不断更新的dir / file),因为这太麻烦了。
The idea is to include only the first level of files and directories in $HOME/ , $HOME/.config/ , and $HOME/.local/share/ . 这个想法是在$HOME/$HOME/.config/$HOME/.local/share/仅包括文件和目录的第一级。 For instance, include .config/foo/ and exclude its contents ie .config/foo/* so I could check the git log when I uninstall a package what directory(es) did it create and remove them manually(of course, if I won't use it anymore) 例如,包括.config/foo/并排除其内容(即.config/foo/*这样我可以在卸载软件包时检查git日志,它创建了哪个目录并手动删除了它们(当然,如果我将不再使用它)

I tried to accomplish this by adding this to my .git/info/exclude 我试图通过将其添加到我的.git/info/exclude来完成此操作

*/*
*/*/*
*/*/*/*
*/*/*/*/*
.local/share/*/*
.local/share/*/*/*
.local/share/*/*/*/*
.local/share/*/*/*/*/*
.config/*/*
.config/*/*/*
.config/*/*/*/*
.config/*/*/*/*/*

because I read that git needs a separate wildcard for every directory level. 因为我读到git在每个目录级别都需要一个单独的通配符。 As you probably have already understood - it didn't work. 您可能已经了解了-这没有用。

So, the question is - how can I monitor only the files and directories in $HOME/ , $HOME/.config/ , and $HOME/.local/share/ without monitoring their contents. 因此,问题是-如何仅监视$HOME/$HOME/.config/$HOME/.local/share/的文件和目录,而不监视它们的内容。 Thanks! 谢谢!

TL;DR TL; DR

What you'll want is to use .gitignore to specifically ignore certain files and subdirectories: 您需要使用.gitignore来专门忽略某些文件和子目录:

*/
!.config
!.config/*
.config/*/
!.local
!.local/*
.local/*/

To see how this works, and what it does (and doesn't do) for you, read the long version. 要了解它是如何工作的,以及它为您做了(和没有做)的内容,请阅读较长的版本。 (The !.config/* is almost certainly unnecessary; I put it in when I had * as part of not saving any top level files, which isn't quite what you asked for. The same holds for !.local/* . Without actually testing it, though, I'm not sure if .config/afile matches the .config rule.) !.config/*几乎肯定是不必要的;我在没有*时将它放入* ,这是不保存任何顶级文件的一部分,这与您要求的不完全相同。 !.local/* 。不过,如果不进行实际测试,则不确定.config/afile.config规则匹配。)

(But note that you probably do want to source-control additional .config files. I also recommend doing this an entirely different way, using symlinks for the .foorc type files—that's what I do.) (但请注意,您可能确实希望对其他.config文件进行源代码控制。我也建议您采用完全不同的方式,对.foorc类型的文件使用符号链接,这就是我要做的。)

Long

There isn't any maximum depth, other than any system-imposed maximum (which varies depending on your OS). 除了系统强加的最大深度(取决于您的操作系统)之外,没有任何最大深度。 But there's a big problem here: Git doesn't store directories. 但是这里存在一个大问题:Git不存储目录。 1 1个

What Git does store, underneath its top level storage item which is the commit , are files (which Git calls blobs ), with associated path names. Git的存储内容是在其顶层存储项(即commit)之下的文件(Git称为blob ),并带有关联的路径名。 If you ask Git to extract commit #1234567..., Git looks inside that commit, finds the path names of the various blobs, and creates directories (new, empty ones) if and when necessary to hold the specific blobs (ie, files) that Git is extracting from that commit with the names they have as stored in that commit. 如果您要求Git提取提交#1234567 ...,则Git会在该提交内部查看,查找各种Blob的路径名,并在必要时创建目录(新的空目录)以保存特定Blob(即文件)。 ),Git正在使用该提交中存储的名称从该提交中提取。

This doesn't mean that your idea is doomed, just that you're starting with a misconception. 这并不意味着您的想法注定要失败,只是您从一个误解入手。 Git won't save the directory .config at all, for instance. 例如,Git根本不会保存目录 .config It will just save the file .config/Trolltech.conf , for instance. 例如,它将仅保存文件 .config/Trolltech.conf If Git has saved that file in some commit, and you git checkout that specific commit, Git will create a new, empty .config if required. 如果Git在某个提交中保存了该文件,并且您git checkout该特定提交,则Git将根据需要创建一个新的空.config If the directory already exists, Git won't do anything about that. 如果目录已经存在,则Git将不执行任何操作。 In some cases, such as moving from a commit in which that file exists to one in which it does not, Git will remove the directory as well, but in some cases it won't, and you will need to use git clean -d to make Git really remove it (if that's possible, ie, if it's empty). 在某些情况下,例如从该文件所在的提交迁移到该文件不存在的提交,Git也会删除该目录,但在某些情况下不会删除该目录,您将需要使用git clean -d使Git真正删除它(如果可能的话,即如果它是空的)。

Having saved that particular file, if Git is being instructed to ignore the subdirectory .config/git , Git may not save the file .config/git/ignore . 保存该特定文件后,如果指示Git忽略子目录.config/git ,则Git可能不会保存该文件 .config/git/ignore This is where things get complicated. 这就是事情变得复杂的地方。 You need to understand how Git commits work, what the index is and how (to some extent) it works, and what Git does to work with, and maintain, a work-tree . 您需要了解Git提交如何工作, 索引是什么以及索引如何工作(在某种程度上)以及Git与工作树一起使用和维护的工作


1 Git does store tree entries, which could work as a flag by which to save empty directories, but other parts of Git combine in strange ways to make this whole concept fail . 1 Git确实存储条目,这可以用作保存空目录的标志,但是Git的其他部分以奇怪的方式组合在一起,使整个概念失败


Git is built around the concept of commits Git围绕提交的概念构建

As we noted above, what Git stores, fundamentally, is the commit. 如上所述,Git从根本上存储的是提交。 A commit is a complete, mostly-standalone snapshot of some set of files, which Git calls blobs . 提交是某些文件集的完整快照,大多数情况下是独立快照,Git称为blobs (This deliberately ignores submodules and symbolic links, but they're stored as blobs as well, using tree entries of a type that distinguishes them from plain files.) I say "mostly-standalone" because each commit records some number of parent commit hash IDs, though most commonly, just one. (这故意忽略了子模块和符号链接,但它们也使用类型区别于普通文件的树条目存储为Blob,它们之所以表示为“斑点”。)我之所以说“大部分是独立的”,是因为每个提交都记录了一定数量的提交哈希ID(尽管最常见)只有一个。 A commit that stores three parent hash IDs depends on those three parent commits' existence: a repository that's missing the three parents is somehow incomplete. 一个存储三个父哈希ID的提交取决于这三个父提交的存在:缺少三个父提交的存储库某种程度上是不完整的。 2 The parent linkage is not important for this particular application, but it's good to know how this works. 2父级链接对于此特定应用程序并不重要,但是最好知道它是如何工作的。

There is, though, one particularly difficult event in the life a commit: creating it. 还有就是,虽然在生活中的一个特别困难的情况下承诺: 在创建它。 Once a commit is created, it is read-only. 创建提交后,它是只读的。 It has a unique hash ID, determined solely by the commit's content (including all its parent hash IDs). 它具有唯一的哈希ID,仅由提交的内容(包括其所有父哈希ID)确定。 But what files go into a commit? 但是哪些文件可以提交? This is the key question and is where .gitignore eventually comes into the picture. 这是关键问题,也是.gitignore最终出现在哪里的地方。


2 This is the essence of a shallow clone. 2这是浅表克隆的本质 A clone that is not shallow (and hence is complete) starts with the tip commits of each branch (and any tagged commits or annotated tag objects). 一个不浅的克隆(因此是完整的)从每个分支的尖端提交(以及所有带标记的提交或带注释的标记对象)开始。 These commits (or annotated tag objects) point back to earlier, ancestor, commits through their parent hash IDs. 这些提交(或带注释的标记对象)通过其父哈希ID指向较早的祖先。 Since the repository is complete, those objects exist as well; 由于存储库已完成,因此这些对象也存在。 they contain their parent hash IDs, and those commit objects exist, and so on. 它们包含父哈希ID和那些犯对象是否存在,等等。 The whole process stops only when we reach some commit(s) that have no parent. 仅当我们到达一些没有父项的提交时,整个过程才会停止。 Usually this is the first commit ever made, which obviously can't have a parent. 通常,这是有史以来的第一次提交,显然没有父母。 Such a commit is called a root commit, and in any non-empty but complete repository, there is always at least one root commit. 这种提交称为提交,在任何非空但完整的存储库中,始终至少有一个根提交。


The files in a new commit are set up in the index 新提交中的文件在索引中设置

Besides the repository itself—the repository being a database of Git objects , ie, commits and blobs and the intermediate thing Git calls a tree (these store the files' names, among other data)—Git has this key data structure with three different names. 除了存储库本身(存储库是Git 对象的数据库,即commit和blob)以及Git称为的中间事物(这些存储文件的名称以及其他数据)之外,Git还具有此密钥数据结构,其中包含三个不同的名称。 It's variously called the index , the staging area , and the cache . 它被不同地称为索引登台区域缓存

The index is normally pretty much invisible. 该索引通常几乎是不可见的。 There is one Git command, git ls-files , that can show you the contents of the index directly ( git ls-files --stage , or even more verbosely, git ls-files --debug ), but it's not really useful to end users. 有一个Git命令git ls-files ,可以直接向您显示索引的内容( git ls-files --stage ,或者更详细地说,是git ls-files --debug ),但是对于终端用户。 A good top-level description of the index, though, is that it's where you build your next commit . 不过,对索引的一个很好的顶层描述是,它是您构建下一个commit的地方

When you run git commit , Git takes every file that is currently in the index, in whatever form it currently has in the index, and makes a new commit out of that. 当您运行git commit ,Git将以索引中当前具有的任何形式获取索引中当前存在的每个文件,并从中进行一个新的提交。 Those are the files stored in the new commit. 这些是存储在新提交中的文件。 The new commit's author and committer are you; 新提交的作者和提交人是您; the time stamp is "now"; 时间戳为“现在”; and the parent of the new commit is whatever commit you had checked out before; 新提交的父级是您之前签出的所有提交; but the files —the blobs and their associated names—are entirely set by whatever is in the index. 但是文件 -blob及其关联的名称-完全由索引中的内容设置。 3 Likewise, when you use git checkout to extract some particular commit, what Git does first is to copy that commit's files into the index. 3同样,当您使用git checkout提取某些特定的提交时,Git首先要做的是将该提交的文件复制到索引中。

Note that when you do make a new commit, that new commit becomes the current commit. 请注意,当您进行新提交时,该新提交将成为当前提交。 When that happens, Git updates the current branch name—the branch you have checked out, such as master —so that it records the new commit. 发生这种情况时,Git会更新当前的分支名称(您已检出的分支,例如master ,以便记录新的提交。 In fact, each branch name records just one hash ID. 实际上,每个分支名称记录一个哈希ID。 Git calls this the tip of the branch. Git将此称为分支的尖端 As we saw in footnote 2 above, Git works backwards , starting from branch tips, to find all the commits contained within a branch. 正如我们在上面的脚注2中所看到的,Git从分支技巧开始向后工作,以查找分支中包含的所有提交。 So making a new commit shoves the new commit's hash ID into the branch name table. 因此,进行新的提交会将新提交的哈希ID推入分支名称表中。


3 Even if you use git commit -a or git commit <file> , Git really just copies files into the index—or sometimes, an (auxiliary) index—and builds the commit from that index. 3即使您使用git commit -agit commit <file> ,Git实际上也只是将文件复制到索引(有时 (辅助)索引)中,并从该索引构建提交。


The work-tree 工作树

All the files stored inside Git, both in the repository and in the index, are in a special, Git-only format. Git内部存储的所有文件,无论是在存储库中还是在索引中,都采用特殊的仅Git格式。 Few if any other programs on the computer can work with these files, so Git extracts each file into a usable version, where you can do work. 计算机上几乎没有其他程序可以使用这些文件,因此Git会将每个文件提取到可用的版本中,您可以在其中进行工作。 This is your work-tree . 这是您的工作树

In general, every file that's in the current commit also appears in the work-tree. 通常, 当前提交中的每个文件也会显示在工作树中。 The current commit is, of course, the one you ran git checkout on. 当然,当前的提交是您对git checkout提交。 If you just ran git checkout master to check out the master branch, what you did in terms of current commit was to check out whatever commit the name master identifies: the tip commit of that branch. 如果您只是运行git checkout master来检出master分支,那么在当前提交方面所做的就是检出master标识的任何提交:该分支的尖端提交。

As we mentioned above, all the files (blob objects) got copied into the index, at that point. 如上所述,所有文件(blob对象)都已复制到索引中。 Git was also able to use whatever was in the index to know what was in your work-tree before that point: for any file that was in the index (and hence in the work-tree) and now isn't in the index because of this checkout, Git should remove that file from the work-tree. Git的是还能够使用任何在索引知道在那个点之前你的工作树:因为这在索引中的任何文件(并因此在工作树),现在是不是在索引中,因为签出后,Git应该从工作树中删除该文件。 And it does! 确实如此! For any file that Git has to replace in the index, or add to the index, Git should copy the index version to the work-tree—and it does. 对于Git必须在索引中替换或添加到索引的任何文件,Git应该将索引版本复制到工作树中,并且确实如此。

What's in the index after the git checkout is exactly whatever blobs are (via any intermediate tree objects) in the commit you checked out. git checkout之后索引中的内容与您检出的提交中的blob(通过任何中间树对象)完全相同。 The work-tree versions of those files will match the index versions of those files, except that the work-tree versions are actually usable. 这些文件的工作树版本将与这些文件的索引版本匹配,但实际上工作树版本可用。 The index versions of those files will match the commit's versions of those files—and in fact, they share the underlying storage, as the index stores just the path names and blob hash IDs. 这些文件的索引版本将与这些文件的提交版本匹配-实际上,它们共享基础存储,因为索引仅存储路径名和Blob哈希ID。

Now, there may be files in the work-tree that Git doesn't know about. 现在,工作树中可能有一些Git 知道的文件。 These files are, by definition, not in the index . 根据定义,这些文件不在index中 These are untracked files . 这些是未跟踪的文件 That is what an untracked file is, in Git: it's a file that's not in the index. 在Git中,这就是未跟踪的文件:它是不在索引中的文件。 There is nothing more to it. 没什么了。

(Well, you can remove a file from the index. Then it's not in the index, and hence untracked. That's not really anything more , but it's worth remembering.) (好吧,您可以从索引中删除文件。然后该文件不在索引中,因此无法进行跟踪。实际上并没有什么更多的 ,但是值得记住。)

Ignoring untracked files 忽略未跟踪的文件

The problem with untracked files is that Git whines about them. 未跟踪文件的问题在于,Git 抱怨它们。 :-) It's constantly griping at you, telling you that files A, B, and C are untracked. :-)它一直困扰着您,告诉您文件A,B和C未被跟踪。 So this is where .gitignore comes in—but .gitignore is about the work-tree , and unlike commits, the work-tree does have directories. 这就是.gitignore进入的地方-但是.gitignore工作树有关 ,与提交不同,工作树确实具有目录。

You can list specific files in .gitignore . 您可以在.gitignore列出特定文件。 If those files are not in the index (are untracked), but are in the work-tree, Git would complain about them ... but then it sees that they're listed in .gitignore and shuts up. 如果这些文件不在索引中(未跟踪),但在工作树中,则Git会抱怨它们……但随后它会看到它们已列在.gitignore并关闭。

You can also git add files en-masse, using git add . 您也可以使用git add .git add文件中git add文件git add . or git add --all . git add --all This has Git scan the work-tree for files, and upon finding them, git add each one to the index, to copy the work-tree version into the builds-the-next-commit index version. 这使Git扫描工作树中的文件,并在找到它们后, git add每个文件git add到索引中,以将工作树版本复制到builds-the-next-commit索引版本中。 Clearly, if files A, B, and C are currently both untracked and ignored, though, Git shouldn't add them. 显然,如果文件A,B和C当前都未跟踪并被忽略,则Git 不应添加它们。 So .gitignore also tells Git not to add existing untracked-and-ignored files to the index. 因此, .gitignore还告诉Git不要将现有的未跟踪和忽略的文件添加到索引中。

Existing files that are in the index are automatically tracked, so any en-masse git add that might potentially add those files, will add them, regardless of what's listed in .gitignore . 索引中的现有文件将自动跟踪,因此任何可能添加这些文件的en-masse git add都将添加它们,无论.gitignore列出的内容如何。 In other words, adding a tracked file to .gitignore has no effect on it. 换句话说,将跟踪文件添加到.gitignore没有影响。 Being in .gitignore only affects untracked files. 位于.gitignore只会影响未跟踪的文件。

But that's files , not directories . 但这是文件 ,而不是目录 This is where everything gets squirrelly. 这就是一切变得松散的地方。 Files exist inside directories, in the normal file system (ie, not in Git, but in the work-tree). 文件存在于普通文件系统中的目录内部(即,不在Git中,而是在工作树中)。

One of the big reasons Git has the index (and calls it the cache ) is that looking at every file in a big file-tree tends to be extremely slow. Git拥有索引 (并称其为cache )的主要原因之一是,查看大文件树中的每个文件往往非常慢。 Git can use the index to record information about all the tracked files, including information that speeds up en-masse git add --all style operations. Git可以使用索引来记录有关所有跟踪文件的信息,包括加快en-masse git add --all样式操作的信息。 That's fine for files that are in the index, but what about for whole subdirectories that (a) aren't in the index, so by definition they're untracked and (b) will be ignored, so they won't go into the index and will remain untracked? 这罚款在索引文件,但是如果是整个子目录(一)不在索引中,因此按照定义它们是未经跟踪和(b)将被忽略,所以他们不会进入索引,并且将保持跟踪状态?

Git can avoid scanning those subdirectories entirely . Git 可以避免完全扫描那些子目录 If .config/dir/ is going to be ignored, and Git has just come across the name .config/dir and it's a directory, why then, Git can just skip reading inside it . 如果.config/dir/将被忽略,而Git刚遇到名称.config/dir并且它是一个目录,那么为什么Git可以跳过其中的阅读 That's a lot faster than reading it and checking every file to see if it should be ignored. 这比读取并检查每个文件以查看是否应该忽略它要快得多。

When Git is scanning the work-tree, it starts at the top and reads the whole contents of the tree: all file names and all sub-directory names. 当Git扫描工作树时,它从顶部开始并读取树的全部内容:所有文件名和所有子目录名。 It knows which are files and which are sub-directories, but it has not yet looked inside any of the subdirectories. 它知道哪些是文件,哪些是子目录,但尚未查看任何子目录。

Now, Git checks all the files: are they in the index? 现在,Git检查所有文件:它们是否在索引中? If so, they're tracked: see if they should be updated. 如果是这样,则会对其进行跟踪:查看是否应对其进行更新。 If not, they're untracked: see if Git should whine about them. 如果没有,那么他们就无法追踪:看看Git是否应该为他们抱怨。

Next, Git checks all the sub-directories. 接下来,Git检查所有子目录。 For each sub-directory: are there any files for it that are in the index? 对于每个子目录:索引中是否有任何文件? If so, the sub-directory must be examined. 如果是这样,则必须检查子目录。 But if not, is the sub-directory ignored? 但是,如果不是,子目录会被忽略吗? If so, don't even look inside it . 如果是这样, 甚至不要看里面 Otherwise, look inside it, just as we would if there were files in the index. 否则,请查看它的内部,就像在索引中有文件一样。

Now, for each file or sub-directory, there can be one or more .gitignore entries. 现在,对于每个文件或子目录,可以有一个或多个.gitignore条目。 An entry ending with * matches files and directories. *结尾的条目匹配文件和目录。 An entry ending with */ matches directories. */结尾的条目与目录匹配。 An entry starting with ! !开头的条目 means: explictly not ignored . 表示: 明确地不被忽略

So, suppose Git is scanning the top level and comes across the name .a , and it's a file. 因此,假设Git正在扫描顶层,并且碰到名称.a ,它是一个文件。 Git will look for any ignore entry matching .a . Git将查找与.a匹配的任何忽略条目。 If there's an entry */ , well, that doesn't match .a ; 如果有*/条目,那不匹配.a ; so .a is added, unless there's a later entry overriding it. 因此添加了.a ,除非以后有覆盖它的条目。 There isn't, so we add the file .a . 没有,因此我们添加文件.a

Next, Git encounters .adir , which is a directory. 接下来,Git遇到.adir ,这是一个目录。 There are no .adir files in the index, so a scan isn't forced , so Git will check for an ignore entry matching .adir . 索引中没有.adir文件,因此没有强制扫描,因此Git将检查匹配.adir的忽略条目。 Since */ is the only match, Git gets to ignore the directory. 由于*/是唯一的匹配项,因此Git可以忽略目录。 It will now not look inside .adir at all (unless and until you somehow add .adir/file to the index, which forces Git to read .adir to check whether .adir/file still exists). 现在,它根本不会在.adir内部.adir (除非,除非您以某种方式将.adir/file添加到索引中,这会迫使Git读取.adir来检查.adir/file是否仍然存在)。

When Git comes across .config (which is a directory), there's a */ that says to ignore it, but it's overridden by !.config which says not to ignore it. 当Git遇到.config (它是一个目录)时,有一个*/表示忽略它,但是它被!.config覆盖,它表示不要忽略它。 There's a .config/* but this is just .config -the-directory, not .config/something . 有一个.config/*但这只是.config -the-directory,而不是.config/something So !.config is the last applicable entry, and Git must scan .config . 所以!.config是最后一个适用的条目,Git必须扫描.config

Sooner or later, 4 Git will look inside .config . 迟早, 4 Git将在.config查找。 It may find .config/afile ; 它可能会找到.config/afile ; this matches !.config/* . 这匹配!.config/* The last entry that it matches tells Git that the file isn't ignored, so it will be added to the index. 它匹配的最后一个条目告诉Git该文件不会被忽略,因此它将被添加到索引中。 Then Git comes across .config/git , which is a directory. 然后Git遇到目录.config/git It matches !.config/* , then .config/*/ ; 它与!.config/*匹配,然后与.config/*/匹配; so it gets ignored. 因此它被忽略。 Git never looks inside .config/git at all. Git根本不会在.config/git内部。

This repeats for the rest of .config . 对于.config的其余部分重复此步骤。 There may be more . 可能还有更多. -files, which Git will process as usual, until Git comes across .local , which works just like .config here. -文件,Git将照常处理,直到Git遇到.local为止,该文件的工作方式与.config

As always, remember that this cannot affect any existing commits. 与往常一样,请记住,这不会影响任何现有的提交。 Checking out any existing commit that has some file that violates the .gitignore rules here will cause Git to extract that file, creating its parent directory or directories if needed. 检出任何包含某些违反.gitignore规则的文件的现有提交,将使Git提取该文件,并在需要时创建其父目录。 Moving from that commit to one that lacks that same file, Git will remove the file, and if the directory containing it goes empty, usually 5 remove the directory as well. 从该提交移到缺少该文件的提交,Git会删除该文件,并且如果包含该目录的目录为空,通常5也会删除该目录。


4 This is where depth-first vs breadth-first scan comes in. Git currently does ASCII-sorted, depth-first directory traversal (so it's actually "right now") because of the way Git organizes the index. 4这就是深度优先与广度优先扫描进入的地方。由于Git组织索引的方式,Git当前进行了ASCII排序的,深度优先的目录遍历(因此实际上是“现在”)。 It doesn't matter from our "what gets ignored and what doesn't" perspective, though. 不过,从我们的“什么被忽略而什么不被忽略”的角度来看,这并不重要。

5 Every once in a while I see weird behavior here that convinces me that there must be some bugs in this. 5我偶尔会在这里看到奇怪的行为,这使我确信其中一定有一些错误。 The occasional git clean -ndf to see what would be cleaned, perhaps followed by git clean -df to actually do the cleaning, is useful. 偶尔使用git clean -ndf来查看将要清除的内容,或者随后使用git clean -df进行实际清洁,这很有用。 But I can never reproduce it, and it's never important enough to try... :-) 但是我永远都无法复制它,并且它永远都不够重要,无法尝试... :-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM