threads / discuss / 17549

retrospective move

Subject: retrospective move

## tl;dr

5 messages between Feb 4, 2009 and Feb 4, 2009.

replies: 4people: 3as markdown or json

jemmyw· Feb 4, 2009, 08:43 UTC · lore

Hi. Awhile ago I copied some files in my local repo then commited. In another commit I deleted the original files, so essentially I did a mv that git doesn't know about. Now I'm trying to merge from another branch where changes have happened to the files in their original locations, and it's making the merge somewhat difficult. Is there a way to ease this?

-- 
View this message in context: http://www.nabble.com/retrospective-move-tp21826423p21826423.html
Sent from the git mailing list archive at Nabble.com.
Thomas Rast· Feb 4, 2009, 09:17 UTC · re: jemmyw · lore

Re: retrospective move

jemmyw wrote:
Show 6 quoted lines
> 
> Hi. Awhile ago I copied some files in my local repo then commited. In another
> commit I deleted the original files, so essentially I did a mv that git
> doesn't know about. Now I'm trying to merge from another branch where
> changes have happened to the files in their original locations, and it's
> making the merge somewhat difficult. Is there a way to ease this?

Git never "knows" about a move, it only detects them after the fact. A corollary of this is that 'git mv a b' is more or less the same as 'cp a b; git rm a; git add b'.

Now since you have a copy followed by a removal in two separate commits, the merge logic won't treat this as a rename. Some ideas for possible solutions:

* If your cp&rm commits are strictly local, you could squash them into
  a single commit.  See man git-rebase(1), especially the -i flag.
  That way, the merge logic will recognise it as a rename.
* You can use an extra line of history that has a "real" one-step mv.
  Suppose you currently have
     *---*---*---*---*      upstream
      \
       \
        +--C--o--R--o--o    local
  where C is the copy and R is the removal.  Then you can introduce an
  extra "gateway" branch that does just the move:
     *---*---*---*---*      upstream
     |\
     | \
     |  +--M                gateway
     |
      \
       \
        +--C--o--R--o--o    local
  Now merge gateway to local (i.e. git checkout local && git merge
  gateway), and resolve all resulting conflicts.  I think you can
  actually use '-s ours' in that merge, but be sure to check the
  results.
     *---*---*---*---*      upstream
     |\
     | \
     |  +--M                gateway
     |      \
      \      +----------+
       \                 \
        +--C--o--R--o--o--1    local
  Finally, you can use the gateway branch to "do the rename" on all
  incoming new changes, by first merging upstream to gateway and later
  again gateway to local, like so:
  
     *---*---*---*---*.      upstream
     |\                \
     | \                \
     |  +--M-------------2       gateway
     |      \             \
      \      +----------+  \
       \                 \  \
        +--C--o--R--o--o--1--3   local
  It won't be very pretty history, but it should only give (rename
  related) merge conflicts at M.
HTH :-)
-- 
Thomas Rast
trast@{inf,student}.ethz.ch
Santi Béjar· Feb 4, 2009, 10:31 UTC · re: Thomas Rast · lore

Re: retrospective move

2009/2/4 Thomas Rast <trast@student.ethz.ch>:
Show 14 quoted lines
> jemmyw wrote:
>>
>> Hi. Awhile ago I copied some files in my local repo then commited. In another
>> commit I deleted the original files, so essentially I did a mv that git
>> doesn't know about. Now I'm trying to merge from another branch where
>> changes have happened to the files in their original locations, and it's
>> making the merge somewhat difficult. Is there a way to ease this?
>
> Git never "knows" about a move, it only detects them after the fact.
> A corollary of this is that 'git mv a b' is more or less the same as
> 'cp a b; git rm a; git add b'.
>
> Now since you have a copy followed by a removal in two separate
> commits, the merge logic won't treat this as a rename.

I don't think it holds. Git only sees the endpoints and the bases, it does not matter if the "move" was actually two commits. Maybe they have changed dramatically and git is not able to detect the rename (or the files are small). Another possible cause could be that you have a lot of renames (see config merge.renamelimit).

You could see if git detects the rename diffing differents commits:
$ git diff --raw -M commit1 commit2
if it does not detect you could use other limits as:
$ git diff --raw -M50 commit1 commit2
while commit1 and 2 are the endpoints or the merge base.

It could hep if you could provide a minimal test case, or at least the commands and output you get.

HTH, Santi

Thomas Rast· Feb 4, 2009, 12:33 UTC · re: Santi Béjar · lore

Re: retrospective move

Santi Béjar wrote:
Show 21 quoted lines
> 2009/2/4 Thomas Rast <trast@student.ethz.ch>:
> > jemmyw wrote:
> >>
> >> Hi. Awhile ago I copied some files in my local repo then commited. In another
> >> commit I deleted the original files, so essentially I did a mv that git
> >> doesn't know about. Now I'm trying to merge from another branch where
> >> changes have happened to the files in their original locations, and it's
> >> making the merge somewhat difficult. Is there a way to ease this?
> >
> > Git never "knows" about a move, it only detects them after the fact.
> > A corollary of this is that 'git mv a b' is more or less the same as
> > 'cp a b; git rm a; git add b'.
> >
> > Now since you have a copy followed by a removal in two separate
> > commits, the merge logic won't treat this as a rename.
> 
> I don't think it holds. Git only sees the endpoints and the bases, it
> does not matter if the "move" was actually two commits. Maybe they
> have changed dramatically and git is not able to detect the rename (or
> the files are small). Another possible cause could be that you have a
> lot of renames (see config merge.renamelimit).
Hmm, you're right.  I stand corrected.
Here's a sequence of commands that makes a test case:
  git init
  seq 1 10 > A
  git add A
  git commit -m initial
  seq 11 15 >> A
  git commit -m second A
  seq 16 20 >> A
  git commit -m third
  git checkout -b side HEAD~2
  cp A B
  git add B
  git commit -m copy
  seq 3 10 > B               # <--
  git commit -m edit B
  git rm A
  git commit -m delete
Then running
  git merge master

goes through cleanly, but changing the line indicated above to 'seq 5 10' makes it less similar, falling through the detection and causing a conflict. The minimum required similarity for merges does not seem to be configurable unfortunately.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch
Santi Béjar· Feb 4, 2009, 12:57 UTC · re: Thomas Rast · lore

Re: retrospective move

2009/2/4 Thomas Rast <trast@student.ethz.ch>:
[...]
Show 27 quoted lines
>
> Here's a sequence of commands that makes a test case:
>
>  git init
>  seq 1 10 > A
>  git add A
>  git commit -m initial
>  seq 11 15 >> A
>  git commit -m second A
>  seq 16 20 >> A
>  git commit -m third
>  git checkout -b side HEAD~2
>  cp A B
>  git add B
>  git commit -m copy
>  seq 3 10 > B               # <--
>  git commit -m edit B
>  git rm A
>  git commit -m delete
>
> Then running
>
>  git merge master
>
> goes through cleanly, but changing the line indicated above to 'seq 5
> 10' makes it less similar, falling through the detection and causing a
> conflict.

Thanks, but that's known, this is why I wrote "or the files are small". I was not clear, but what I wanted was a *real* test case. In which cases this is a problem in practice.

>  The minimum required similarity for merges does not seem to
> be configurable unfortunately.
No, but nevertheless it works fine most of the time, except with small files.

HTH, Santi

← back to recent threads