Jak powiedzieć gitowi, aby zawsze wybierał moją wersję lokalną do scalania w konflikcie w określonym pliku?

100

Powiedzmy, że współpracuję z kimś za pośrednictwem repozytorium git i istnieje konkretny plik, do którego nigdy nie chcę akceptować żadnych zmian zewnętrznych.

Czy jest jakiś sposób, abym skonfigurować lokalne repozytorium, aby nie narzekać na konfliktowe scalanie za każdym razem, gdy wyciągam? Chciałbym zawsze wybierać moją lokalną wersję podczas scalania tego pliku.

saffsd
źródło
1
Właśnie dodałem proste rozwiązanie za pośrednictwem .gitattributes i bardzo podstawowego „sterownika scalającego”
VonC
12
TD; LR:echo 'path/to/file merge=ours' >> .gitattributes && git config --global merge.ours.driver true
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
@CiroSantilli: Działa jak urok w Linuksie. Ten sterownik jest na tyle prosty, że można go
wbudować
Czy chcesz przesłać zmiany do pliku? Czy jest to na przykład plik konfiguracyjny, w którym wartość domyślna jest przechowywana w git.
Ian Ringrose
Komentarz @CiroSantilli 新疆 改造 中心 六四 事件 法轮功 jest poprawny, ale sprawi, że takie zachowanie wystąpi w przypadku każdego repozytorium w Twoim systemie ze --globalznacznikiem. Jeśli chcesz to zachowanie tylko dla pojedynczego repozytorium, pomiń --globalflagę:echo 'path/to/file merge=ours' >> .gitattributes && git config merge.ours.driver true
majorobot

Odpowiedzi:

140

W konkretnym przypadku pliku konfiguracyjnego zgodziłbym się z odpowiedzią Rona :
konfiguracja powinna być „prywatna” dla twojego obszaru roboczego (stąd „ignorowana”, jak w przypadku „zadeklarowanej w .gitignorepliku”).
Możesz mieć szablon pliku konfiguracyjnego z tokenizowanymi wartościami i skrypt przekształcający ten config.templateplik w prywatny (i ignorowany) plik konfiguracyjny.


Jednak ta konkretna uwaga nie odpowiada na szersze, bardziej ogólne pytanie, tj. Twoje pytanie (!):

Jak powiedzieć gitowi, aby zawsze wybierał moją wersję lokalną do scalania w konflikcie w określonym pliku? (dla dowolnego pliku lub grupy plików)

Ten rodzaj scalania to „scalanie kopii”, w którym zawsze kopiujesz „naszą” lub „ich” wersję pliku, gdy wystąpi konflikt.

(jak zauważa Brian Vandenberg w komentarzach , ours” i „ theirs” są tutaj używane do scalania .
Są one odwrócone w przypadku rebase : patrz „ Why is the meaning of “ours” and “theirs” reversed with git-svn”, który używa rebase, ” git rebase, śledzenie„ lokalnego ”i„ zdalnego ” " )

W przypadku „pliku” (ogólnie pliku, nie mówiąc o pliku „konfiguracyjnym”, ponieważ jest to zły przykład), można to osiągnąć za pomocą niestandardowego skryptu wywoływanego przez scalanie.
Git wywoła ten skrypt, ponieważ będziesz musiał zdefiniować wartość gitattributes , która definiuje niestandardowy sterownik scalania .

„Niestandardowy sterownik scalania” jest w tym przypadku bardzo prostym skryptem, który zasadniczo zachowuje niezmienioną wersję bieżącej wersji, dzięki czemu można zawsze wybrać lokalną wersję.

Tj., Jak zauważył przez Ciro Santilli :

echo 'path/to/file merge=ours' >> .gitattributes
git config --global merge.ours.driver true

Przetestujmy to w prostym scenariuszu, z msysgit 1.6.3 w systemie Windows, w zwykłej sesji DOS:

cd f:\prog\git\test
mkdir copyMerge\dirWithConflicts
mkdir copyMerge\dirWithCopyMerge
cd copyMerge
git init
Initialized empty Git repository in F:/prog/git/test/copyMerge/.git/

Teraz stwórzmy dwa pliki, które będą miały konflikty, ale które zostaną scalone w inny sposób.

echo a > dirWithConflicts\a.txt
echo b > dirWithCopyMerge\b.txt
git add -A
git commit -m "first commit with 2 directories and 2 files"
[master (root-commit) 0adaf8e] first commit with 2 directories and 2 files

Wprowadzimy „konflikt” w zawartości obu tych plików w dwóch różnych gałęziach git:

git checkout -b myBranch
Switched to a new branch 'myBranch'
echo myLineForA >> dirWithConflicts\a.txt
echo myLineForB >> dirWithCopyMerge\b.txt
git add -A
git commit -m "add modification in myBranch"
[myBranch 97eac61] add modification in myBranch

git checkout master
Switched to branch 'master'
git checkout -b hisBranch
Switched to a new branch 'hisBranch'
echo hisLineForA >> dirWithConflicts\a.txt
echo hisLineForB >> dirWithCopyMerge\b.txt
git add -A
git commit -m "add modification in hisBranch"
[hisBranch 658c31c] add modification in hisBranch

Teraz spróbujmy połączyć „hisBranch” z „myBranch” z:

  • ręczne rozwiązywanie konfliktów połączeń
  • z wyjątkiem sytuacji, w dirWithCopyMerge\b.txtktórych zawsze chcę zachować moją wersję b.txt.

Ponieważ scalanie następuje w „ MyBranch”, wrócimy do niego i dodamy gitattributesdyrektywy „ ”, które dostosują zachowanie scalania.

git checkout myBranch
Switched to branch 'myBranch'
echo b.txt merge=keepMine > dirWithCopyMerge\.gitattributes
git config merge.keepMine.name "always keep mine during merge"
git config merge.keepMine.driver "keepMine.sh %O %A %B"
git add -A
git commit -m "prepare myBranch with .gitattributes merge strategy"
[myBranch ec202aa] prepare myBranch with .gitattributes merge strategy

Mamy .gitattributesplik zdefiniowany w dirWithCopyMergekatalogu (zdefiniowany tylko w gałęzi, w której nastąpi scalenie:) myBranchi mamy .git\configplik, który zawiera teraz sterownik scalania.

[merge "keepMine"]
        name = always keep mine during merge
        driver = keepMine.sh %O %A %B

Jeśli jeszcze nie zdefiniowałeś keepMine.sh i mimo to uruchomisz scalanie, oto co otrzymasz.

git merge hisBranch
sh: keepMine.sh: command not found
fatal: Failed to execute internal merge
git st
# On branch myBranch
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   dirWithConflicts/a.txt
#
no changes added to commit (use "git add" and/or "git commit -a")

type dirWithConflicts\a.txt
a
<<<<<<< HEAD:dirWithConflicts/a.txt
myLineForA
=======
hisLineForA
>>>>>>> hisBranch:dirWithConflicts/a.txt

W porządku:

  • a.txt jest gotowy do połączenia i ma w sobie konflikt
  • b.txtjest nadal nietknięty, ponieważ sterownik merge ma się tym zająć (ze względu na dyrektywę w .gitattributespliku w jego katalogu).

Zdefiniuj keepMine.shdowolne miejsce w swoim %PATH%(lub $PATHdla naszego znajomego Uniksa. Oczywiście robię oba: mam sesję Ubuntu w sesji VirtualBox)

Jak skomentował przez lrkwz i opisane w „ Merge Strategies sekcji” z Dostosowywanie Git - Git Atrybuty można zastąpić skrypt z poleceniem powłoki true.

git config merge.keepMine.driver true

Ale w ogólnym przypadku możesz zdefiniować plik skryptu:

keepMine.sh

# I want to keep MY version when there is a conflict
# Nothing to do: %A (the second parameter) already contains my version
# Just indicate the merge has been successfully "resolved" with the exit status
exit 0

(to był jeden prosty kierowca seryjnej;) (jeszcze prostsze w tym przypadku należy użyć true)
(Jeśli chcesz zachować drugą wersję, wystarczy dodać przed exit 0linią:
cp -f $3 $2.
To wszystko scalić kierowca wynos zachować wersję pochodzących z drugiej. oddział, nadpisując wszelkie zmiany lokalne)

Teraz spróbujmy ponownie scalić od początku:

git reset --hard
HEAD is now at ec202aa prepare myBranch with .gitattributes merge strategy

git merge hisBranch
Auto-merging dirWithConflicts/a.txt
CONFLICT (content): Merge conflict in dirWithConflicts/a.txt
Auto-merging dirWithCopyMerge/b.txt
Automatic merge failed; fix conflicts and then commit the result.

Scalanie kończy się niepowodzeniem ... tylko dla pliku.txt .
Edytuj plik a.txt i opuść linię z „hisBranch”, a następnie:

git add -A
git commit -m "resolve a.txt by accepting hisBranch version"
[myBranch 77bc81f] resolve a.txt by accepting hisBranch version

Sprawdźmy, czy plik b.txt został zachowany podczas tego scalania

type dirWithCopyMerge\b.txt
b
myLineForB

Ostatnie zatwierdzenie reprezentuje pełne scalenie:

git show -v 77bc81f5e
commit 77bc81f5ed585f90fc1ca5e2e1ddef24a6913a1d
Merge: ec202aa 658c31c
git merge hisBranch
Already up-to-date.

(Wiersz zaczynający się od Merge to potwierdza)


Weź pod uwagę, że możesz zdefiniować, połączyć i / lub nadpisać sterownik scalający, tak jak Git:

  • zbadać <dir>/.gitattributes(który znajduje się w tym samym katalogu, co dana ścieżka): będzie miał pierwszeństwo przed innymi .gitattributesw katalogach
  • Następnie sprawdza .gitattributes(który znajduje się w katalogu nadrzędnym), ustawi dyrektywy tylko wtedy, gdy nie zostały jeszcze ustawione
  • Wreszcie bada $GIT_DIR/info/attributes. Ten plik jest używany do nadpisywania ustawień w drzewie. Zastąpi <dir>/.gitattributesdyrektywy.

Przez „łączenie” rozumiem „zagregowany” sterownik wielokrotnego scalania.
Nick Green próbuje w komentarzach faktycznie połączyć sterowniki scalające: zobacz „ Łączenie pom za pomocą sterownika Python git ”.
Jednak, jak wspomniał w innym pytaniu , działa tylko w przypadku konfliktów (równoczesna modyfikacja w obu gałęziach).

VonC
źródło
1
Dziękuję za szczegółową odpowiedź! Rozumiem, że nie ma sensu używać plików konfiguracyjnych do kontroli wersji, ale szukałem prostego, motywującego przykładu. Rzeczywiście, interesowało mnie szersze pytanie. Nigdy wcześniej nie słyszałem o sterownikach git merge, więc dziękuję za oświecenie mnie.
saffsd
6
cp -f $3 $2Prawdopodobnie powinny być cytowane, tj cp -f "$3" "$2".
Arc
1
@VonC dzięki za szczegółową odpowiedź! Problem polega na tym, że zależy to od ludzi ustawiających sterownik w swoim pliku .git / config. Chciałbym dodać informacje o sterowniku do samego projektu, aby było to automatyczne i wymagało mniej pracy konfiguracyjnej. Jakieś wskazówki?
Juan Delgado,
2
@ulmangt: możesz bardzo dużo przechowywać ten skrypt również w repozytorium git, ... o ile znajdziesz sposób na dodanie jego katalogu nadrzędnego do PATH(Unix lub Windows PATH). Ponieważ ten skrypt będzie interpretowany przez powłokę Unix bash lub przez powłokę MingWin bash MsysGit Windows, będzie on przenośny.
VonC
5
@ VonC Thanks. Jeszcze jeden problem. W pewnych okolicznościach (jeśli nie było żadnych zmian w lokalnej gałęzi, do której jest scalana) wydaje się, że sterownik scalania nigdy nie jest nawet wywoływany, co powoduje modyfikację plików lokalnych (które powinny używać niestandardowego sterownika scalania aby zapobiec ich zmianie podczas łączenia). Czy istnieje sposób, aby wymusić na git, aby zawsze używał sterownika scalającego?
ulmangt
1

Jak skomentował @ ciro-santilli, prosty sposób na użycie .gitattributesz jego ustawieniami:

path/to/file merge=ours

i włącz tę strategię za pomocą:

git config --global merge.ours.driver true

(Dodam to jako odpowiedź, aby uczynić ją bardziej widoczną, ale czyniąc z niej Wiki społeczności, aby nie próbować przekraczać kredytów użytkownika dla siebie. Proszę o głosowanie w górę jego komentarza pod Q, aby dać mu uznanie!)

Greg Dubicki
źródło
(Poza tym: jeśli ktoś daje odpowiedź jako komentarz i nie dodaje odpowiedzi, jest całkowicie w porządku napisać odpowiedź inną niż CW i otrzymać kredyty. Jeśli nadal jest aktywnym członkiem, możesz pingować go, aby dodać odpowiedź, jeśli chcesz, ale technicznie już mieli swoją szansę :-)).
halfer
0

Mamy wiele plików konfiguracyjnych, których nigdy nie chcemy nadpisywać. Jednak .gitignore i .gitattributes nie działały w naszej sytuacji. Naszym rozwiązaniem było przechowywanie plików konfiguracyjnych w gałęzi configs. Następnie zezwól na zmianę plików podczas scalania git, ale natychmiast po scaleniu użyj opcji „git checkout branch -”. aby skopiować nasze pliki konfiguracyjne z gałęzi configs po każdym scaleniu. Szczegółowa odpowiedź na temat przepełnienia stosu tutaj

HamletHub
źródło