复制文件及其整个历史记录

时间:2013-06-27 12:31:41

标签: git

我和另一位开发人员正在开发由其他代码访问的API。当我们更改API的行为以更好地满足我们的需求时,我们会发布API的其他版本,而不会弃用旧版本,因此使用API​​的现有应用程序不必立即更新。例如:

$ cat 0.5.php
<?php
$username = $_POST['name'];
?>

$ cat 0.6.php
<?php
$username = $_POST['username'];
?>

当我们开始一个新版本时,我们通常会cp将N-1.php版本化为N.php并从那里开始编码。但是,如果我们使用Git执行此操作,那么我们会从文件中丢失整个blamediff和其他历史记录以进行比较和还原。 如何将旧文件的这段历史记录到新文件中,使blamelogdiff和这些命令“正常工作”而不向他们展示其他标志或参数,例如--follow

6 个答案:

答案 0 :(得分:10)

您想使用-C标志。这将检测副本以及重命名,因此它可以跟踪历史记录。 diffblamelog接受此标记。

就像@ madara-uchiha说的那样:你应该看看使用标签,并且可能会从他们那里生成你的git-x.y文件。您可以使用以下内容来获取给定标记处文件的内容:

git show v0.6:git.php > git-0.6.php

v0.6是您感兴趣的标记。

<强>更新

这是一个小脚本。第一个假设您的代码的格式为x.yx.y.z

#!/bin/bash
versions=$(git tag -l | egrep -o '^[0-9]+\.[0-9]+(\.[0-9]+)?$')
for version in $versions
do
    git show $version:git.php > git-$version.php
done

如果您的标记格式为vX.YvX.Y.Z,并且您希望git-x.y.phpgit-x.y.z.php作为文件名,则可以使用:

#!/bin/bash
versions=$(git tag -l | egrep -o '^v[0-9]+\.[0-9]+(\.[0-9]+)?$')
for version in $versions
do
    git show $version:git.php > git-${version/#v/}.php
done

在发布过程中运行此脚本,它将为您生成所有版本。此外,从名称中删除git-非常容易。例如,> git-$version.php变为> $version.php

答案 1 :(得分:6)

我认为你有点混淆,因为你似乎试图将版本控制处理事物的方式与API暴露的方式结合起来(即Web服务器如何处理事情)。

为了使API的多个版本同时工作,消费者可能需要指定他们想要用于给定调用的版本。出于本答案的目的,我假设您的工作方式与Stack Exchange API类似,因此将版本指定为API URL的第一个“目录”组件(例如,对于版本1.5,我将我的请求指向http://domain.tld/1.5/call,版本1.6我使用http://domain/1.6/?method=call等等)。但实际上这个元素并不重要,只要你有一些机制来确定适当的版本并将请求路由到Web服务器级别的正确控制器。

版本控制

我在这里采取的方法相当简单。每个版本都在存储库中获得自己的分支。针对该版本执行的任何开发工作都可以在版本分支的分支中完成,也可以直接提交到版本。 Master总是包含最新的稳定版本。

例如,假设当前版本为1.5,并且当前所有内容都在master下,并且您没有历史分支。在当前稳定代码下绘制一条线,并创建一个名为1.5的分支。现在,要在1.6上开始开发,它将构建在1.5分支上,从master创建一个新的分支并将其命名为1.6。

任何适用于1.6的开发都发生在1.6分支或使用1.6作为基础创建的其他分支中。这意味着一切都可以很好,干净地推入/拉入1.6分支。

如果您需要在1.5版本中应用小错误修复,则可以在1.5分支中轻松执行此操作。如果你想从1.6分支中提取,你需要“挑选”它 - 因为分支已经开始分歧,任何这样的问题都需要手动处理以确保最大限度地安全地保护“稳定” “代码库。

当创建1.7 / 2.0 /无论什么时候,将1.6版本拉入master,标记它,并为新版本创建一个新分支。

通过这种方式,每个版本/发行版的用户和时间的完整历史记录存储在分支中。正如其他人所提到的,不要忘记标记里程碑版本。

网络服务器

使用上述方法,Web服务器设置维护起来相当简单。每个版本的根目录都与相应的分支同步。

因此,为了简单起见,我们假设版本控制中存储库的根目录对应于API代码的文档根目录(实际上这不太可能是这种情况,但是一些URL重写或类似的方法可以解决这个问题。)

在Web服务器上的域的文档根目录中,我们创建以下目录结构:

<document-root>
    |
    |--- 1.5
    |
    |--- 1.6

在1.5,1.6目录中的每一个中,我们从中央版本控制克隆存储库,并切换到相应的分支。每当您希望实时更改时,只需从相应分支中的版本控制中下拉更改。

在高容量环境中,您可能有一整个服务器专用于为版本标识符作为子域提供服务的每个版本,但同样的一般原则适用 - 除了可以将存储库直接克隆到每个服务器的文档根目录。

为新分支创建目录,将repo克隆到其中并切换到适当的分支,以及为版本下拉补丁/错误修正的很多(如果不是全部)过程可以使用scripts / cron自动完成等等,但在你这样做之前不要忘记:在没有人为参与的情况下将更改推送到实时服务器通常会以泪水结束。


另一种方法

...将创建一个单个父存储库,用作域的文档根。在此,您将在每个版本的存储库根目录中创建子模块。这将产生的整体效果非常相似,但具有“优势”,即只需在服务器上同步单个存储库,并保持由版本控制定义的Web服务器的目录结构。但是,就个人而言,我不喜欢这种方法,原因有两个:

  • 子模块很难维护。它们附加到特定的提交中,很容易忘记它。
  • 我相信分支驱动方法所提供的控制更精细,更准确地说明发生了什么。

我接受这两个原因主要是个人偏好,这就是为什么我提出这个原因。

答案 2 :(得分:2)

这是一个愚蠢的黑客,但似乎接近你想要的行为。请注意,它假设您已将最早提交的0.5.php标记为first

  1. 分支

    % git checkout -b tmp

  2. 制作补丁文件夹和补丁版本的0.5.php文件的提交历史记录

    % mkdir patches && git format-patch first 0.5.php -o patches

  3. 删除您的文件并签出第一份副本

    % rm 0.5.php && git checkout first -- 0.5.php

  4. 重命名您的文件

    % mv 0.5.php 0.6.php

  5. 调整您的补丁文件以使用新名称

    % sed 's/0\.5\.php/0\.6\.php/g' -i patches/0*

  6. 提交(如果您还没有几次)

    % git add -A && git commit -m'ready for history transfer'

  7. 应用补丁

    % git am -s patches/0*

  8. 返回主人,拉过新文件并删除tmp分支

    % git co master && git co tmp -- 0.6.php && git branch -D tmp

  9. 瞧!你是0.6.php文件现在有一个复制你的0.5.php文件的历史记录,除了0.6.php历史记录中的每个提交将具有0.5.php历史记录中的唯一ID。时代和责任应该仍然是正确的。只需稍加努力,您就可以将所有这些内容放在脚本中,然后将脚本别名为git cp

答案 3 :(得分:2)

警告:以下命令rewrites history通常是不受欢迎的  在共享存储库上。在推-f之前先想想。

重写您的历史记录,以包含git filter-branch文件的第二个副本:

git filter-branch --tree-filter 'if [ -f 0.5.php ]; then cp 0.5.php 0.6.php; fi' HEAD

现在,0.6.php是历史上0.5.php的精确复制品。


然后你的同事需要处理新的历史。

How do I recover/resynchronise after someone pushes a rebase or a reset to a published branch?

答案 4 :(得分:1)

结帐git subtreealso here)。有了它,您必须能够用该文件拆分部分历史记录。如果其他人使用交互式rebase,你也可以复制它。然后你可以将它合并回来,并有副本。

答案 5 :(得分:1)

更标准的方法是只有一个api.php文件,并使用分支和标记来标记新版本。

至于提供文件:如果你想为你的用户提供几个版本的api,可以使用一些部署过程来检查和构建api的特定版本,重命名并按照你的意愿移动,并设置公共访问权限 - 不是你的开发树。