Слияние, не изменяя рабочий каталог

У меня есть следующий сценарий:

* ab82147 (HEAD, topic) changes
* 8993636 changes
* 82f4426 changes
* 18be5a3 (master) first

Я хотел бы объединиться (не ускоренная перемотка вперед) topic в master. Это требует меня к:

  • git checkout master
  • git merge --no-ff topic

Но проверка ведущего устройства и затем слияние темы в него заставляют мерзавца изменять мой рабочий каталог (хотя конечный результат идентичен тому перед проверкой ведущего устройства), и проблема, которую я имею с этим, происходит из-за размера нашего проекта, требуется приблизительно 30 минут для создания его (с IncrediBuild), хотя ничто действительно не изменилось, и это просто невыносимо.

Таким образом, то, что я хотел бы получить, следующее:

*   9075cf4 (HEAD, master) Merge branch 'topic'
|\  
| * ab82147 (topic) changes
| * 8993636 changes
| * 82f4426 changes
|/  
* 18be5a3 first

Действительно не касаясь рабочего каталога (или по крайней мере обманывая мерзавца так или иначе).

16
задан Idan K 4 August 2010 в 18:22
поделиться

4 ответа

Интересно! Я не думаю, что есть встроенный способ сделать это, но у вас должна быть возможность подделать это с помощью сантехники:

#!/bin/bash

branch=master
# or take an argument:
# if [ $@ eq 1 ];
#      branch="$1";
# fi

# make sure the branch exists
if ! git rev-parse --verify --quiet --heads "$branch" > /dev/null; then
     echo "error: branch $branch does not exist"
     exit 1
fi

# make sure this could be a fast-forward   
if [ "$(git merge-base HEAD $branch)" == "$(git rev-parse $branch)" ]; then
    # find the branch name associated with HEAD
    currentbranch=$(git symbolic-ref HEAD | sed 's@.*/@@')
    # make the commit
    newcommit=$(echo "Merge branch '$currentbranch'" | git commit-tree $(git log -n 1 --pretty=%T HEAD) -p $branch -p HEAD)
    # move the branch to point to the new commit
    git update-ref -m "merge $currentbranch: Merge made by simulated no-ff" "refs/heads/$branch" $newcommit
else
    echo "error: merging $currentbranch into $branch would not be a fast-forward"
    exit 1
fi

Интересный момент в том, что строка newcommit = ; он использует дерево фиксации для непосредственного создания фиксации слияния. Первый аргумент - это дерево, которое нужно использовать; это HEAD дерева, ветка, содержимое которой вы хотите сохранить. Сообщение о фиксации передается на стандартный ввод, а остальные аргументы указывают на родителей, которые должна иметь новая фиксация. SHA1 фиксации выводится на стандартный вывод, поэтому, если фиксация выполнена успешно, вы фиксируете ее, а затем объединяете эту фиксацию (это будет перемотка вперед). Если вы одержимы, вы можете убедиться, что дерево фиксации прошло успешно, но это должно быть в значительной степени гарантировано.

Ограничения:

  • Это работает только для слияний, которые могли быть перемоткой вперед. Очевидно, что в этом случае вам действительно придется проверить и выполнить слияние (возможно, в клоне, чтобы сохранить вашу систему сборки).
  • Сообщение рефлога другое. Я сделал это намеренно, потому что, когда вы используете - no-ff , git фактически заставит себя использовать стратегию по умолчанию (рекурсивную), но писать это в журнале ссылок было бы ложью.
  • Если вы находитесь в режиме отдельной HEAD, все пойдет плохо. К этому нужно относиться особо.

И да, я тестировал это на игрушечном репо, и, похоже, он работает правильно! (Хотя я и не старался его сломать.)

8
ответ дан 30 November 2019 в 23:09
поделиться

Самый простой способ, который я могу придумать, - это git clone в отдельную рабочую копию, выполнить слияние там, а затем git pull ] назад. В этом случае вытягивание будет перемоткой вперед и затронет только те файлы, которые действительно изменились.

Конечно, для такого большого проекта создание временных клонов не идеально и требует значительного количества дополнительного места на жестком диске. Временные затраты на дополнительный клон можно минимизировать (в долгосрочной перспективе), сохраняя вашу объединяемую копию, пока вам не понадобится дисковое пространство.

Отказ от ответственности: я не проверял, работает ли это. Я считаю, что это должно быть (git не проверяет временные метки файлов)

3
ответ дан 30 November 2019 в 23:09
поделиться

Вот как бы обманная версия.

  1. git stash
  2. git tag tmptag
  3. git merge --no-ff topic
  4. git checkout tmptag (-b tha_brunch)?
  5. git stash pop
  6. git tag -D tmptag
0
ответ дан 30 November 2019 в 23:09
поделиться

В качестве альтернативы, вы можете устранить симптомы напрямую, сохраняя и восстанавливая временные метки файлов. Это довольно уродливо, но было интересно написать.

Python Timestamp Save/Restore Script

#!/usr/bin/env python

from optparse import OptionParser
import os
import subprocess
import cPickle as pickle

try:
    check_output = subprocess.check_output
except AttributeError:
    # check_output was added in Python 2.7, so it's not always available
    def check_output(*args, **kwargs):
        kwargs['stdout'] = subprocess.PIPE
        proc = subprocess.Popen(*args, **kwargs)
        output = proc.stdout.read()
        retcode = proc.wait()
        if retcode != 0:
            cmd = kwargs.get('args')
            if cmd is None:
                cmd = args[0]
            err = subprocess.CalledProcessError(retcode, cmd)
            err.output = output
            raise err
        else:
            return output

def git_cmd(*args):
    return check_output(['git'] + list(args), stderr=subprocess.STDOUT)

def walk_git_tree(rev):
    """ Generates (sha1,path) pairs for all blobs (files) listed by git ls-tree. """
    tree = git_cmd('ls-tree', '-r', '-z', rev).rstrip('\0')
    for entry in tree.split('\0'):
        print entry
        mode, type, sha1, path = entry.split()
        if type == 'blob':
            yield (sha1, path)
        else:
            print 'WARNING: Tree contains a non-blob.'

def collect_timestamps(rev):
    timestamps = {}
    for sha1, path in walk_git_tree(rev):
        s = os.lstat(path)
        timestamps[path] = (sha1, s.st_mtime, s.st_atime)
        print sha1, s.st_mtime, s.st_atime, path
    return timestamps

def restore_timestamps(timestamps):
    for path, v in timestamps.items():
        if os.path.isfile(path):
            sha1, mtime, atime = v
            new_sha1 = git_cmd('hash-object', '--', path).strip()
            if sha1 == new_sha1:
                print 'Restoring', path
                os.utime(path, (atime, mtime))
            else:
                print path, 'has changed (not restoring)'
        elif os.path.exists(path):
            print 'WARNING: File is no longer a file...'

def main():
    oparse = OptionParser()
    oparse.add_option('--save',
        action='store_const', const='save', dest='action',
        help='Save the timestamps of all git tracked files')
    oparse.add_option('--restore',
        action='store_const', const='restore', dest='action',
        help='Restore the timestamps of git tracked files whose sha1 hashes have not changed')
    oparse.add_option('--db',
        action='store', dest='database',
        help='Specify the path to the data file to restore/save from/to')

    opts, args = oparse.parse_args()
    if opts.action is None:
        oparse.error('an action (--save or --restore) must be specified')

    if opts.database is None:
        repo = git_cmd('rev-parse', '--git-dir').strip()
        dbpath = os.path.join(repo, 'TIMESTAMPS')
        print 'Using default database:', dbpath
    else:
        dbpath = opts.database

    rev = git_cmd('rev-parse', 'HEAD').strip()
    print 'Working against rev', rev

    if opts.action == 'save':
        timestamps = collect_timestamps(rev)
        data = (rev, timestamps)
        pickle.dump(data, open(dbpath, 'wb'))
    elif opts.action == 'restore':
        rev, timestamps = pickle.load(open(dbpath, 'rb'))
        restore_timestamps(timestamps)

if __name__ == '__main__':
    main()

Bash Test Script

#!/bin/bash

if [ -d working ]; then
    echo "Cowardly refusing to mangle an existing 'working' dir."
    exit 1
fi

mkdir working
cd working

# create the repository/working copy
git init

# add a couple of files
echo "File added in master:r1." > file-1
echo "File added in master:r1." > file-2
mkdir dir
echo "File added in master:r1." > dir/file-3
git add file-1 file-2 dir/file-3
git commit -m "r1: add-1, add-2, add-3"
git tag r1
# sleep to ensure new or changed files won't have the same timestamp
echo "Listing at r1"
ls --full-time
sleep 5

# make a change
echo "File changed in master:r2." > file-2
echo "File changed in master:r2." > dir/file-3
echo "File added in master:r2." > file-4
git add file-2 dir/file-3 file-4
git commit -m "r2: change-2, change-3, add-4"
git tag r2
# sleep to ensure new or changed files won't have the same timestamp
echo "Listing at r2"
ls --full-time
sleep 5

# create a topic branch from r1 and make some changes
git checkout -b topic r1
echo "File changed in topic:r3." > file-2
echo "File changed in topic:r3." > dir/file-3
echo "File added in topic:r3." > file-5
git add file-2 dir/file-3 file-5
git commit -m "r3: change-2, change-3, add-5"
git tag r3
# sleep to ensure new or changed files won't have the same timestamp
echo "Listing at r3"
ls --full-time
sleep 5

echo "Saving timestamps"
../save-timestamps.py --save

echo "Checking out master and merging"
# merge branch 'topic'
git checkout master
git merge topic
echo "File changed in topic:r3." > file-2 # restore file-2
echo "File merged in master:r4." > dir/file-3
git add file-2 dir/file-3
git commit -m "r4: Merge branch 'topic'"
git tag r4
echo "Listing at r4"
ls --full-time

echo "Restoring timestamps"
../save-timestamps.py --restore
ls --full-time

Я оставлю читателю упражнение на очистку Python-скрипта, чтобы убрать лишние выводы и добавить лучшую проверку ошибок.

0
ответ дан 30 November 2019 в 23:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: