Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git copy file preserving history [duplicate]

People also ask

Does git copy a file?

Its heuristics usually work if you move a file, but doesn't if you copy it: the copy will appear to have been created ex nihilo by the commit. This script makes copies of a file using git mv but keeps the original, which is then moved back to its original name.

Is there a git copy command?

Usage. git clone is primarily used to point to an existing repo and make a clone or copy of that repo at in a new directory, at another location. The original repository can be located on the local filesystem or on remote machine accessible supported protocols. The git clone command copies an existing Git repository.

Does git save history?

Git stores the complete history of your files for a project in a special directory (a.k.a. a folder) called a repository, or repo.

How do I copy and paste a file in git?

If you wish to copy and paste from the web or an external file, use the normal method of highlighting the text and hitting the copy option, or ctrl+c shortcut, to copy the said text. Head straight to your CMD or Git Bash and hover your mouse pointer over the window and right-click.


All you have to do is:

  1. move the file to two different locations,
  2. merge the two commits that do the above, and
  3. move one copy back to the original location.

You will be able to see historical attributions (using git blame) and full history of changes (using git log) for both files.

Suppose you want to create a copy of file foo called bar. In that case the workflow you'd use would look like this:

git mv foo bar
git commit

SAVED=`git rev-parse HEAD`
git reset --hard HEAD^
git mv foo copy
git commit

git merge $SAVED     # This will generate conflicts
git commit -a        # Trivially resolved like this

git mv copy foo
git commit

Why this works

After you execute the above commands, you end up with a revision history that looks like this:

( revision history )            ( files )

    ORIG_HEAD                      foo
     /     \                      /   \
SAVED       ALTERNATE          bar     copy
     \     /                      \   /
      MERGED                     bar,copy
        |                           |
     RESTORED                    bar,foo

When you ask Git about the history of foo, it will:

  1. detect the rename from copy between MERGED and RESTORED,
  2. detect that copy came from the ALTERNATE parent of MERGED, and
  3. detect the rename from foo between ORIG_HEAD and ALTERNATE.

From there it will dig into the history of foo.

When you ask Git about the history of bar, it will:

  1. notice no change between MERGED and RESTORED,
  2. detect that bar came from the SAVED parent of MERGED, and
  3. detect the rename from foo between ORIG_HEAD and SAVED.

From there it will dig into the history of foo.

It's that simple. :)

You just need to force Git into a merge situation where you can accept two traceable copies of the file(s), and we do this with a parallel move of the original (which we soon revert).


Unlike subversion, git does not have a per-file history. If you look at the commit data structure, it only points to the previous commits and the new tree object for this commit. No explicit information is stored in the commit object which files are changed by the commit; nor the nature of these changes.

The tools to inspect changes can detect renames based on heuristics. E.g. "git diff" has the option -M that turns on rename detection. So in case of a rename, "git diff" might show you that one file has been deleted and another one created, while "git diff -M" will actually detect the move and display the change accordingly (see "man git diff" for details).

So in git this is not a matter of how you commit your changes but how you look at the committed changes later.


Simply copy the file, add and commit it:

cp dir1/A.txt dir2/A.txt
git add dir2/A.txt
git commit -m "Duplicated file from dir1/ to dir2/"

Then the following commands will show the full pre-copy history:

git log --follow dir2/A.txt

To see inherited line-by-line annotations from the original file use this:

git blame -C -C -C dir2/A.txt

Git does not track copies at commit-time, instead it detects them when inspecting history with e.g. git blame and git log.

Most of this information comes from the answers here: Record file copy operation with Git


I've slightly modified Peter's answer here to create a reusable, non-interactive shell script called git-split.sh:

#!/bin/sh

if [[ $# -ne 2 ]] ; then
  echo "Usage: git-split.sh original copy"
  exit 0
fi

git mv "$1" "$2"
git commit -n -m "Split history $1 to $2 - rename file to target-name"
REV=`git rev-parse HEAD`
git reset --hard HEAD^
git mv "$1" temp
git commit -n -m "Split history $1 to $2 - rename source-file to temp"
git merge $REV
git commit -a -n -m "Split history $1 to $2 - resolve conflict and keep both files"
git mv temp "$1"
git commit -n -m "Split history $1 to $2 - restore name of source-file"

For completeness, I would add that, if you wanted to copy an entire directory full of controlled AND uncontrolled files, you could use the following:

git mv old new
git checkout HEAD old

The uncontrolled files will be copied over, so you should clean them up:

git clean -fdx new